it-swarm.com.de

std :: map einfügen oder std :: map finden?

Angenommen, Sie möchten auf einer Karte vorhandene Einträge beibehalten. In 20% der Fälle handelt es sich bei dem von Ihnen eingegebenen Eintrag um neue Daten. Gibt es einen Vorteil, wenn Sie std :: map :: find dann std :: map :: insert mit diesem zurückgegebenen Iterator ausführen? Oder ist es schneller, das Einfügen zu versuchen und dann basierend darauf zu handeln, ob der Iterator angibt, dass der Datensatz eingefügt wurde oder nicht?

84
Superpolock

Die Antwort ist, dass Sie beides nicht tun. Stattdessen möchten Sie etwas tun, das in Punkt 24 von Effective STL von Scott Meyers vorgeschlagen wird:

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
}
138
luke

Die Antwort auf diese Frage hängt auch davon ab, wie teuer es ist, den Werttyp zu erstellen, den Sie in der Karte speichern:

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

Bei einem Wertetyp wie int ist der obige Wert effizienter als ein Suchvorgang mit anschließender Einfügung (sofern keine Compiler-Optimierungen vorgenommen wurden). Dies liegt, wie oben erwähnt, daran, dass die Suche in der Karte nur einmal stattfindet.

Der Aufruf zum Einfügen setzt jedoch voraus, dass Sie bereits den neuen "Wert" erstellt haben:

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

Um 'insert' aufzurufen, zahlen wir für den teuren Aufruf zur Erstellung unseres Wertetyps - und von dem, was Sie in der Frage gesagt haben, werden Sie diesen neuen Wert in 20% der Fälle nicht verwenden. Wenn im obigen Fall das Ändern des Kartenwerttyps keine Option ist, ist es effizienter, zuerst die Suche durchzuführen, um zu überprüfen, ob das Element erstellt werden muss.

Alternativ kann der Wertetyp der Karte geändert werden, um Handles für die Daten mit Ihrem bevorzugten Smart Pointer-Typ zu speichern. Der Aufruf zum Einfügen verwendet einen Nullzeiger (sehr billig zu konstruieren) und nur bei Bedarf wird der neue Datentyp konstruiert.

11
Richard Corden

Es wird kaum einen Geschwindigkeitsunterschied zwischen den 2 geben, find gibt einen Iterator zurück, insert macht dasselbe und durchsucht die Karte trotzdem, um festzustellen, ob der Eintrag bereits existiert.

Also .. es liegt an den persönlichen Vorlieben. Ich versuche immer, einzufügen und bei Bedarf zu aktualisieren, aber einige Leute mögen es nicht, das zurückgegebene Paar zu behandeln.

8
gbjbaanb

Ich würde denken, wenn Sie eine Entdeckung dann einfügen, würden die zusätzlichen Kosten sein, wenn Sie den Schlüssel nicht finden und die Einfügung danach durchführen. Es ist so, als würde man Bücher in alphabetischer Reihenfolge durchsehen und das Buch nicht finden und dann noch einmal durch die Bücher schauen, um zu sehen, wo man es einlegen kann. Es kommt darauf an, wie Sie mit den Schlüsseln umgehen und ob sie sich ständig ändern. Jetzt gibt es eine gewisse Flexibilität, wenn Sie es nicht finden, können Sie protokollieren, Ausnahme, tun, was Sie wollen ...

5
PiNoYBoY82

Ich habe die beste Antwort verloren.

Find gibt map.end () zurück, wenn es nichts findet, was bedeutet, dass Sie dann neue Dinge hinzufügen

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

ist doppelt so langsam wie

map.insert

für jedes Element, das nicht bereits in der Karte enthalten ist, da es zweimal suchen muss. Einmal, um zu sehen, ob es da ist, noch einmal, um den Platz zu finden, an dem das Neue steht.

3
gman

Ich habe anscheinend nicht genug Punkte, um einen Kommentar zu hinterlassen, aber die angekreuzte Antwort scheint mir langwierig zu sein - wenn Sie bedenken, dass diese Einfügung den Iterator trotzdem zurückgibt, warum sollten Sie nach lower_bound suchen, wenn Sie einfach den zurückgegebenen Iterator verwenden können. Seltsam.

1
Stonky

Wenn Sie sich Gedanken über die Effizienz machen, sollten Sie sich hash_map <> ansehen.

Normalerweise wird map <> als binärer Baum implementiert. Abhängig von Ihren Anforderungen kann eine hash_map effizienter sein.

1
Adam Tegen

Alle Antworten zur Effizienz hängen von der genauen Implementierung Ihrer STL ab. Die einzige Möglichkeit, dies sicher zu wissen, besteht darin, ein Benchmarking in beide Richtungen durchzuführen. Ich nehme an, dass der Unterschied unwahrscheinlich ist. Entscheiden Sie sich also für den Stil, den Sie bevorzugen.

0
Mark Ransom