web-dev-qa-db-fra.com

Quel est le moyen le plus rapide de changer la clé d’un élément dans std :: map

Je comprends les raisons pour lesquelles on ne peut pas simplement faire cela (rééquilibrage et autres):

iterator i = m.find(33);

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

Mais jusqu'à présent, le seul moyen (à ma connaissance) de changer de clé est de supprimer complètement le nœud de l'arbre, puis d'insérer la valeur avec une clé différente:

iterator i = m.find(33);

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

Cela me semble plutôt inefficace pour plus de raisons:

  1. traverse l'arbre trois fois (+ balance) au lieu de deux fois (+ balance)
  2. encore une copie inutile de la valeur
  3. désallocation inutile puis réallocation d'un noeud à l'intérieur de l'arbre

Je trouve que l'allocation et la désallocation sont les pires de ces trois. Est-ce que je manque quelque chose ou existe-t-il un moyen plus efficace de le faire?

UPDATE: Je pense qu'en théorie, cela devrait être possible, donc je ne pense pas que le changement pour une structure de données différente soit justifié. Voici le pseudo-algorithme auquel je pense:

  1. trouvez le noeud dans l'arbre dont je veux changer la clé.
  2. détache si de l'arbre (ne pas désallouer)
  3. rééquilibrer
  4. changer la clé à l'intérieur du noeud détaché
  5. réinsère le noeud dans l'arbre
  6. rééquilibrer
42
Peter Jankuliak

En C++ 17, la nouvelle fonction map::extract vous permet de modifier la clé.
Exemple:

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" } }
21
21koizyd

J'ai proposé votre algorithme pour les conteneurs associatifs il y a environ 18 mois ici:

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

Recherchez le commentaire intitulé: [2009-09-19, Howard ajoute:].

À l'époque, nous étions trop proches de FDIS pour envisager ce changement. Cependant, je pense que c'est très utile (et vous êtes apparemment d'accord), et j'aimerais le mettre dans TR2. Peut-être pourriez-vous nous aider en trouvant et en notifiant votre représentant d’organe national C++ que c’est une fonctionnalité que vous souhaiteriez voir.

Mettre à jour

Ce n'est pas certain, mais je pense qu'il y a de bonnes chances que nous voyions cette fonctionnalité en C++ 17! :-)

28
Howard Hinnant

Vous pouvez omettre la copie de 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);
}
23
Viktor Sehr

Les clés des cartes STL doivent être immuables.

On dirait que peut-être une structure ou des structures de données différentes pourrait être plus logique si la volatilité est trop importante du côté clé de vos paires.

6
Joe

Vous ne pouvez pas.

Comme vous l'avez remarqué, ce n'est pas possible. Une mappe est organisée de sorte que vous puissiez modifier la valeur associée à une clé efficacement, mais pas l'inverse.

Vous avez un aperçu de Boost.MultiIndex, et notamment de ses sections Emulating Standard Container . Les conteneurs Boost.MultiIndex offrent une mise à jour efficace.

5
Matthieu M.

Vous devriez laisser l'allocation à l'allocateur. :-)

Comme vous le dites, lorsque la clé change, il peut y avoir beaucoup de rééquilibrage. C'est comme ça qu'un arbre fonctionne. Peut-être 22 est le premier noeud de l'arbre et 33 le dernier? Que savons-nous?

Si éviter les allocations est important, vous devriez peut-être essayer un vecteur ou un deque? Ils allouent des morceaux plus volumineux, ils économisent donc sur le nombre d'appels à l'allocateur, mais gaspillent potentiellement de la mémoire. Tous les conteneurs ont leurs compromis et il vous appartient de décider lequel présente le principal avantage dont vous avez besoin dans chaque cas (en supposant que cela compte vraiment).

Pour les aventuriers:
Si vous savez avec certitude que le changement de clé n'affecte pas l'ordre et que vous ne faites jamais d'erreur, un petit const_cast voudrait vous permet de changer la clé de toute façon.

1
Bo Persson

Si vous savez que la nouvelle clé est valide pour la position sur la carte (le modifier ne modifiera pas l'ordre) et que vous ne voulez pas le travail supplémentaire de suppression et d'ajout de l'élément à la carte, vous pouvez utiliser un const_cast pour modifier la clé, comme dans unsafeUpdateMapKeyInPlace ci-dessous:

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;
}

Si vous souhaitez une solution qui ne change sur place que lorsque celle-ci est valide, sinon change la structure de la carte:

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