potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++ std::multimap

標準ライブラリのSTLコンテナ、キーの重複を許すマップstd::multimap(以下multimap)についてのメモです。

初期化子リストによる初期化

キーと値のペアをstd::pairや初期化子リスト{key, value}の初期化子リストとして与えることで初期化できます。キーは重複可能で、値の数だけペアを指定します。std::map<KeyT, std::vector<ValueT>>のように初期化リストによる値の一括指定はできません。

なお、初期化子リストを使用した場合、元の値はコピーあるいは通常の構築に使用されます。ムーブが必要な場合はstd::moveを使用します。

// C++17
#include <map>
#include <string>
#include <iostream>

using namespace std;

int main()
{
    // キーは文字列、値は整数のmultimapを作成する。
    multimap<wstring, int> map{
        {L"a", 0},
        {L"a", 1},
        {L"a", 2},
        {L"b", 0},
        {L"b", 0},
        {L"a", 3}};

    // 要素を列挙する。
    for (const auto& [key, value] : map)
    {
        wcout << key << L", " << value << endl;
    }
}

出力

a, 0
a, 1
a, 2
a, 3
b, 0
b, 0
  • 上記では省略していますが、multimapはテンプレート引数としてキーの比較方法を受け取ります。サンプルコードで初期化子リストの最後に指定した{L"a", 3}が出力では途中に来ているように、追加したペアはキーを比較して並べ替えられます。
  • {L"b", 0}{L"b", 0}のような重複したペアもそれぞれ保持されます。
  • イテレーターはキーと値のペアを指します。したがって、範囲ベースforの要素はstd::pair<キーの型, 値の型>です。これはauto&や構造化束縛でも受け取れます。

C++14以前では構造化束縛を使用できないため、auto&std::pairを明記します。

// C++14
#include <map>
#include <string>
#include <iostream>

using namespace std;

int main()
{
    multimap<wstring, int> map{
        {L"a", 0},
        {L"a", 1},
        {L"a", 2},
        {L"b", 0},
        {L"b", 0},
        {L"a", 3}};

    // const pair<wstring, int>& entry
    for (const auto& entry : map)
    {
        wcout << entry.first << L", " << entry.second << endl;
    }
}

素数やキーに対応した値の個数の取得

素数multimap.size、キーに対応した値の数はmultimap.countで取得できます。後者は存在しないキーを指定すると0を返します。また、キーがstd::wstringの場合はmultimap.count(nullptr)で実行時エラーが発生します(MSVC)。

#include <map>
#include <string>
#include <iostream>

using namespace std;

int main()
{
    multimap<wstring, int> map{
        {L"a", 0}, {L"a", 1}, {L"a", 2},
        {L"b", 0}, {L"b", 0}, {L"a", 3}};

    wcout << map.size() << endl; // 6

    wcout << map.count(L"a") << endl; // 4
    wcout << map.count(L"b") << endl; // 2
    wcout << map.count(L"")  << endl; // 0
    wcout << map.count(L"c") << endl; // 0
    // wcout << map.count(nullptr) << endl; // wstringの場合は実行時エラー
}

要素の追加

multimap.insertmultimap.emplacemultimap.emplace_hintにより要素を追加できます。どの関数でもキーと値のペアを指定します。

#include <map>      // multimap
#include <string>   // wstring
#include <iostream> // wcout, endl, etc.

using namespace std;

int main()
{
    wstring s1(L"s1");
    wstring s2(L"s2");
    wstring s3(L"s3");
    wstring s4(L"s4");
    wstring s5(L"s5");
    wstring s6(L"s6");
    wstring s7(L"s7");
    wstring s8(L"s8");
    wstring s9(L"s9");

    multimap<wstring, int> map;

    // 構築(キーは"s1"~"s9"を引数に初期化されます)
    map.insert({ L"s1", 1 });
    map.insert(pair(L"s2", 2));
    map.insert(make_pair(L"s3", 3));
    map.emplace(L"s4", 4);
    map.emplace(pair(L"s5", 5));
    map.emplace(make_pair(L"s6", 6));
    map.emplace_hint(map.cend(), L"s7", 7);
    map.emplace_hint(map.cend(), pair(L"s8", 8));
    map.emplace_hint(map.cend(), make_pair(L"s9", 9));

    wcout << L"\"" << s1 << s2 << s3 << s4 << s5 << s6 << s7 << s8 << s9 << L"\"" << endl;

    // コピー(キーはs1~s9のコピーで初期化されます)
    map.insert({ s1, 1 });
    map.insert(pair(s2, 2));
    map.insert(make_pair(s3, 3));
    map.emplace(s4, 4);
    map.emplace(pair(s5, 5));
    map.emplace(make_pair(s6, 6));
    map.emplace_hint(map.cend(), s7, 7);
    map.emplace_hint(map.cend(), pair(s8, 8));
    map.emplace_hint(map.cend(), make_pair(s9, 9));

    wcout << L"\"" << s1 << s2 << s3 << s4 << s5 << s6 << s7 << s8 << s9 << L"\"" << endl;

    // ムーブ(キーはs1~s9をムーブして初期化されます)
    map.insert({ move(s1), 1 });
    map.insert(pair(move(s2), 2));
    map.insert(make_pair(move(s3), 3));
    map.emplace(move(s4), 4);
    map.emplace(pair(move(s5), 5));
    map.emplace(make_pair(move(s6), 6));
    map.emplace_hint(map.cend(), move(s7), 7);
    map.emplace_hint(map.cend(), pair(move(s8), 8));
    map.emplace_hint(map.cend(), make_pair(move(s9), 9));

    wcout << L"\"" << s1 << s2 << s3 << s4 << s5 << s6 << s7 << s8 << s9 << L"\"" << endl;
    wcout << endl;

    for (const auto& [key, value] : map)
    {
        wcout << key << L", " << value << endl;
    }
}

出力

"s1s2s3s4s5s6s7s8s9"
"s1s2s3s4s5s6s7s8s9"
""

s1, 1
s1, 1
s1, 1
s2, 2
s2, 2
s2, 2
s3, 3
s3, 3
s3, 3
s4, 4
s4, 4
s4, 4
s5, 5
s5, 5
s5, 5
s6, 6
s6, 6
s6, 6
s7, 7
s7, 7
s7, 7
s8, 8
s8, 8
s8, 8
s9, 9
s9, 9
s9, 9

あるキーを持つ要素の列挙

multimap::equal_rangemultimap::lower_boundmultimap::upper_boundであるキーを持つ要素を列挙できます。前者は先頭・番兵イテレーターのペア(std::pair)を返し、後者はそれらを個別に取得します。イテレーターを直接使用して通常のfor文でも記述できますが、C++20からはstd::ranges::subrangeを使うことで範囲ベースforも使用できます。

lower_boundupper_boundsubrangeを使用する例(C++20以降)

#include <map>
#include <string>
#include <iostream>
#include <ranges>

using namespace std;

int main()
{
    multimap<wstring, int> map{ {L"a", 0}, {L"a", 1}, {L"b", 0}, {L"b", 0} };

    for (const auto& [key, value] : ranges::subrange(map.lower_bound(L"a"), map.upper_bound(L"a")))
    {
        wcout << key << L", " << value << endl;
    }
}

equals_rangesubrangeを使用する例(C++20以降)

#include <map>
#include <string>
#include <iostream>
#include <ranges>

using namespace std;

template <typename IterT>
constexpr ranges::subrange<IterT> make_subrange(std::pair<IterT, IterT> pair) noexcept
{
    return ranges::subrange(pair.first, pair.second);
}

int main()
{
    multimap<wstring, int> map{ {L"a", 0}, {L"a", 1}, {L"b", 0}, {L"b", 0} };

    for (const auto& [key, value] : make_subrange(map.equal_range(L"a")))
    {
        wcout << key << L", " << value << endl;
    }
}

出力

a, 0
a, 1

あるキーを持つ要素をまとめて削除

multimap.eraseを使います。

#include <map>
#include <string>

using namespace std;

int main()
{
    multimap<wstring, int> map{ {L"a", 0}, {L"a", 1}, {L"b", 0}, {L"b", 0} };

    map.erase(L"a"); // {L"b", 0}, {L"b", 0}だけ残る。
}

その他

multimapは他にも通常のSTLコンテナの持つメンバー関数などを持ちます。詳細はcppreferencecpprefjpの記事が詳しく書かれています。