web-dev-qa-db-ja.com

std :: map内の要素のキーを変更する最も速い方法は何ですか

私はこれを行うことができない理由を理解しています(リバランスとか):

iterator i = m.find(33);

if (i != m.end())
  i->first = 22;

しかし、これまでのところ、キーを変更する唯一の方法(私が知っている)は、ツリーからノードをすべて削除してから、別のキーで値を挿入し直すことです。

iterator i = m.find(33);

if (i != m.end())
{
  value = i->second;
  m.erase(i);
  m[22] = value;
}

これはもっと多くの理由で私にはかなり非効率的なようです:

  1. ツリーを2回(+バランス)ではなく3回(+バランス)トラバースします
  2. 値のもう1つの不必要なコピー
  3. 不要な割り当て解除と、ツリー内のノードの再割り当て

割り当てと割り当て解除は、これら3つの中で最悪だと思います。私は何かを見逃していますか、それを行うためのより効率的な方法はありますか?

更新:理論的には可能であるべきだと思うので、異なるデータ構造に変更することは正当化されるとは思わない。私が念頭に置いている擬似アルゴリズムは次のとおりです。

  1. キーを変更するノードをツリー内で見つけます。
  2. ツリーからifをデタッチします(割り当て解除しないでください)
  3. リバランス
  4. 切り離されたノード内のキーを変更する
  5. ノードをツリーに挿入します
  6. リバランス
49
Peter Jankuliak

C++ 17では、新しい map::extract 関数を使用すると、キーを変更できます。
例:

std::map<int, std::string> m{ {10, "potato"}, {1, "banana"} };
auto nodeHandler = m.extract(10);
nodeHandler.key() = 2;
m.insert(std::move(nodeHandler)); // { { 1, "banana" }, { 2, "potato" } }
35
21koizyd

約18か月前に、ここで連想コンテナのアルゴリズムを提案しました。

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#839

マークされたコメントを探してください:[2009-09-19 Howard added:]。

当時、私たちはこの変更を検討するにはFDISに近すぎました。しかし、私はそれが非常に便利だと思います(そして、あなたは明らかに同意します)、そして、私はそれをTR2に入れたいです。おそらく、C++ National Bodyの代表者を見つけて通知することで、これがあなたが見たい機能であることを助けることができます。

更新

確かではありませんが、C++ 17でこの機能を見る可能性は十分にあると思います! :-)

31
Howard Hinnant

value;のコピーは省略できます。

const int oldKey = 33;
const int newKey = 22;
const iterator it = m.find(oldKey);
if (it != m.end()) {
  // Swap value from oldKey to newKey, note that a default constructed value 
  // is created by operator[] if 'm' does not contain newKey.
  std::swap(m[newKey], it->second);
  // Erase old key-value from map
  m.erase(it);
}
24
Viktor Sehr

STLマップのキーは不変である必要があります。

ペアリングのキー側にそれほど多くのボラティリティがある場合、おそらく異なるデータ構造(複数可)がより理にかなっているようです。

6
Joe

できません。

お気づきのように、それは不可能です。マップは、キーに関連付けられた値を効率的に変更できるように編成されていますが、その逆はできません。

Boost.MultiIndex、特にその Emulating Standard Containerセクション をご覧ください。 Boost.MultiIndexコンテナーは、効率的な更新を特徴としています。

5
Matthieu M.

割り当てはアロケーターに任せる必要があります。 :-)

おっしゃるように、キーが変更されると、多くのリバランスが発生する可能性があります。それがツリーの仕組みです。おそらく22はツリーの最初のノードで、33は最後のノードでしょうか?私たちは何を知っていますか?

割り当てを回避することが重要な場合、おそらくベクトルまたはデックを試してみてください。これらは大きなチャンクに割り当てられるため、アロケーターの呼び出し回数を節約できますが、代わりにメモリを浪費する可能性があります。すべてのコンテナにはトレードオフがあり、それぞれのケースで必要な主な利点を持っているコンテナを決定するのはあなた次第です(それが重要であると仮定すると)。

冒険好きのために:
キーの変更が順序に影響しないことを確信しており、間違いを犯すことがない場合、少しconst_castwouldとにかくキーを変更します。

1
Bo Persson

新しいキーがマップの位置に対して有効であることがわかっている場合(変更しても順序は変わりません)、アイテムをマップに追加したり削除したりする余分な作業が必要ない場合は、const_cast以下のunsafeUpdateMapKeyInPlaceのようにキーを変更します。

template <typename K, typename V, typename It>
bool isMapPositionValidForKey (const std::map<K, V>& m, It it, K key)
{
    if (it != m.begin() && std::prev (it)->first >= key)
        return false;
    ++it;
    return it == m.end() || it->first > key;
}

// Only for use when the key update doesn't change the map ordering
// (it is still greater than the previous key and lower than the next key).
template <typename K, typename V>
void unsafeUpdateMapKeyInPlace (const std::map<K, V>& m, typename std::map<K, V>::iterator& it, K newKey)
{
    assert (isMapPositionValidForKey (m, it, newKey));
    const_cast<K&> (it->first) = newKey;
}

有効な場合にのみインプレースで変更し、それ以外の場合はマップ構造を変更するソリューションが必要な場合:

template <typename K, typename V>
void updateMapKey (const std::map<K, V>& m, typename std::map<K, V>::iterator& it, K newKey)
{
    if (isMapPositionValidForKey (m, it, newKey))
    {
        unsafeUpdateMapKeyInPlace (m, it, newKey);
        return;
    }
    auto next = std::next (it);
    auto node = m.extract (it);
    node.key() = newKey;
    m.insert (next, std::move (node));
}
0
yairchu