std :: map insert ou std :: map find?


90

En supposant une carte où vous souhaitez conserver les entrées existantes. 20% du temps, l'entrée que vous insérez est de nouvelles données. Y a-t-il un avantage à faire std :: map :: find puis std :: map :: insert en utilisant l'itérateur retourné? Ou est-il plus rapide de tenter l'insertion et d'agir ensuite selon que l'itérateur indique ou non que l'enregistrement a été inséré ou non?


4
J'ai été corrigé et j'avais l'intention d'utiliser std :: map :: lower_bound au lieu de std :: map :: find.
Superpolock

Réponses:


147

La réponse est que vous ne faites ni l'un ni l'autre. Au lieu de cela, vous voulez faire quelque chose suggéré par l'article 24 de Effective STL par Scott Meyers :

typedef map<int, int> MapType;    // Your map type may vary, just change the typedef

MapType mymap;
// Add elements to map here
int k = 4;   // assume we're searching for keys equal to 4
int v = 0;   // assume we want the value 0 associated with the key of 4

MapType::iterator lb = mymap.lower_bound(k);

if(lb != mymap.end() && !(mymap.key_comp()(k, lb->first)))
{
    // key already exists
    // update lb->second if you care to
}
else
{
    // the key does not exist in the map
    // add it to the map
    mymap.insert(lb, MapType::value_type(k, v));    // Use lb as a hint to insert,
                                                    // so it can avoid another lookup
}

2
C'est en effet ainsi que fonctionne la recherche, l'astuce est que cela combine la recherche nécessaire à la recherche et à l'insertion. Bien sûr, il en va de même en utilisant simplement insérer, puis en regardant la deuxième valeur de retour.
puetzk

1
Deux questions: 1) En quoi l'utilisation de lower_bound est-elle différente de l'utilisation de find pour une carte? 2) Pour une 'map', n'est-il pas vrai que la main droite de && est toujours vraie quand 'lb! = Mymap.end ()'?
Richard Corden

11
@Richard: find () retourne end () si la clé n'existe pas, lower_bound retourne la position où l'élément devrait être (qui à son tour peut être utilisé comme indice d'insertion). @puetzek: Est-ce que "juste insérer" n'écraserait pas la valeur référente des clés existantes? On ne sait pas si l'OP le souhaite.
peterchen

2
quelqu'un sait s'il y a quelque chose de similaire pour unordered_map?
Giovanni Funchal

3
@peterchen map :: insert n'écrase pas la valeur existante si elle existe, voir cplusplus.com/reference/map/map/insert .
Chris Drew

11

La réponse à cette question dépend également du coût de création du type de valeur que vous stockez dans la carte:

typedef std::map <int, int> MapOfInts;
typedef std::pair <MapOfInts::iterator, bool> IResult;

void foo (MapOfInts & m, int k, int v) {
  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.second->second = v;
  }
}

Pour un type valeur tel qu'un int, ce qui précède sera plus efficace qu'une recherche suivie d'une insertion (en l'absence d'optimisations du compilateur). Comme indiqué ci-dessus, cela est dû au fait que la recherche sur la carte n'a lieu qu'une seule fois.

Cependant, l'appel à insérer nécessite que vous ayez déjà construit la nouvelle "valeur":

class LargeDataType { /* ... */ };
typedef std::map <int, LargeDataType> MapOfLargeDataType;
typedef std::pair <MapOfLargeDataType::iterator, bool> IResult;

void foo (MapOfLargeDataType & m, int k) {

  // This call is more expensive than a find through the map:
  LargeDataType const & v = VeryExpensiveCall ( /* ... */ );

  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.second->second = v;
  }
}

Afin d'appeler «insert», nous payons l'appel coûteux pour construire notre type de valeur - et d'après ce que vous avez dit dans la question, vous n'utiliserez pas cette nouvelle valeur 20% du temps. Dans le cas ci-dessus, si la modification du type de valeur de la carte n'est pas une option, il est alors plus efficace d'effectuer d'abord la «recherche» pour vérifier si nous devons construire l'élément.

Vous pouvez également modifier le type de valeur de la carte pour stocker les poignées des données à l'aide de votre type de pointeur intelligent préféré. L'appel à insérer utilise un pointeur nul (très bon marché à construire) et seulement si nécessaire le nouveau type de données est construit.


8

Il n'y aura pratiquement aucune différence de vitesse entre les 2, find renverra un itérateur, insert fait de même et cherchera quand même sur la carte pour déterminer si l'entrée existe déjà.

Donc ... c'est une question de préférence personnelle. J'essaie toujours d'insérer puis de mettre à jour si nécessaire, mais certaines personnes n'aiment pas gérer la paire renvoyée.


5

Je pense que si vous faites une recherche puis insérez, le coût supplémentaire serait lorsque vous ne trouvez pas la clé et effectuez l'insertion après. C'est un peu comme regarder les livres par ordre alphabétique et ne pas trouver le livre, puis regarder à nouveau les livres pour voir où l'insérer. Cela se résume à la façon dont vous gérerez les clés et si elles changent constamment. Maintenant, il y a une certaine flexibilité en ce que si vous ne le trouvez pas, vous pouvez vous connecter, exception, faire ce que vous voulez ...


3

Je suis perdu sur la première réponse.

Find renvoie map.end () s'il ne trouve rien, ce qui signifie que si vous ajoutez de nouvelles choses alors

iter = map.find();
if (iter == map.end()) {
  map.insert(..) or map[key] = value
} else {
  // do nothing. You said you did not want to effect existing stuff.
}

est deux fois plus lent que

map.insert

pour tout élément qui ne figure pas déjà sur la carte car il devra rechercher deux fois. Une fois pour voir si c'est là, encore une fois pour trouver l'endroit où mettre la nouvelle chose.


1
Une version de STL insert renvoie une paire contenant un itérateur et un booléen. Le booléen indique s'il l'a trouvé ou non, l'itérateur est soit l'entrée trouvée, soit l'entrée insérée. C'est difficile à battre pour l'efficacité; impossible, je dirais.
Zan Lynx

4
Non, la réponse cochée utilisée lower_bound, non find. Par conséquent, si la clé n'a pas été trouvée, elle a renvoyé un itérateur au point d'insertion, pas à la fin. En conséquence, c'est plus rapide.
Steven Sudit

1

Si vous êtes préoccupé par l'efficacité, vous pouvez consulter hash_map <> .

Typiquement, la carte <> est implémentée sous la forme d'un arbre binaire. Selon vos besoins, un hash_map peut être plus efficace.


J'aurais adoré. Mais il n'y a pas de hash_map dans la bibliothèque standard C ++, et les PHB n'autorisent pas le code en dehors de cela.
Superpolock

1
std :: tr1 :: unordered_map est la carte de hachage qui est proposée pour être ajoutée à la prochaine norme, et devrait être disponible dans la plupart des implémentations actuelles de la STL.
beldaz

1

Je ne semble pas avoir assez de points pour laisser un commentaire, mais la réponse cochée me semble être longue - si vous considérez que l'insert renvoie l'itérateur de toute façon, pourquoi chercher lower_bound, quand vous pouvez simplement utiliser l'itérateur retourné. Étrange.


1
Parce que (certainement avant C ++ 11) l'utilisation d'insert signifie que vous devez toujours créer un std::map::value_typeobjet, la réponse acceptée évite même cela.
KillianDS

-1

Toute réponse sur l'efficacité dépendra de la mise en œuvre exacte de votre STL. La seule façon de savoir avec certitude est de le comparer dans les deux sens. Je suppose que la différence ne sera probablement pas significative, alors décidez en fonction du style que vous préférez.


1
Ce n'est pas tout à fait vrai. La STL est différente de la plupart des autres bibliothèques en ce qu'elle fournit des exigences big-O explicites pour la plupart de ses opérations. Il existe une différence garantie entre 2 * O (log n) et 1 * O (log n), quelle que soit l'implémentation que les fonctions utilisent pour obtenir ce comportement O (log n). Que cette différence soit significative ou non sur votre plateforme est une autre question. Mais la différence sera toujours là.
srm

@srm définissant les exigences big-O ne vous dit toujours pas combien de temps une opération prendra en termes absolus. La différence garantie dont vous parlez n'existe pas.
Mark Ransom

-2

map [key] - laissez stl le trier. C'est communiquer votre intention le plus efficacement.

Ouais, assez bien.

Si vous faites une recherche, puis une insertion, vous effectuez 2 x O (log N) lorsque vous obtenez un échec car la recherche vous permet seulement de savoir si vous devez insérer pas où l'insert doit aller (lower_bound pourrait vous aider) . Juste un insert direct et ensuite examiner le résultat est la voie que j'irais.


Non, si l'entrée existe, elle renvoie une référence à l'entrée existante.
Kris Kumler

2
-1 pour cette réponse. Comme Kris K l'a dit, l'utilisation de map [key] = value écrasera l'entrée existante, et non la «conservera» comme requis dans la question. Vous ne pouvez pas tester l'existence en utilisant la carte [clé], car elle renverra un objet construit par défaut si la clé n'existe pas, et le créera comme entrée pour la clé
netjeff

Le but est de tester si la carte est déjà remplie et d'ajouter / d'écraser uniquement si elle n'y est pas. L'utilisation de map [key] suppose que la valeur est toujours là.
srm
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.