std :: map insert o std :: map find?


90

Supponendo una mappa in cui si desidera conservare le voci esistenti. Il 20% delle volte la voce che stai inserendo è di nuovi dati. C'è un vantaggio nel fare std :: map :: find quindi std :: map :: insert usando l'iteratore restituito? O è più veloce tentare l'inserimento e quindi agire in base al fatto che l'iteratore indichi o meno il record è stato inserito o meno?


4
Sono stato corretto e avevo intenzione di utilizzare std :: map :: lower_bound invece di std :: map :: find.
Superpolock

Risposte:


147

La risposta è che non fai nessuno dei due. Invece vuoi fare qualcosa suggerito dall'articolo 24 di Efficace STL di 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
Questo è infatti il ​​modo in cui funziona Find, il trucco è che questo combina la ricerca necessaria per trovare e inserire. Ovviamente, lo stesso vale per l'inserimento e quindi per esaminare il secondo valore restituito.
puetzk

1
Due domande: 1) In che modo l'utilizzo di lower_bound è diverso dall'utilizzo di find per una mappa? 2) Per una "mappa", non è forse vero che la mano destra op di && è sempre vera quando "lb! = Mymap.end ()"?
Richard Corden,

11
@Richard: find () restituisce end () se la chiave non esiste, lower_bound restituisce la posizione in cui dovrebbe essere l'elemento (che a sua volta può essere utilizzata come suggerimento per l'inserimento). @puetzek: "Basta inserire" non sovrascriverebbe il valore di riferimento per le chiavi esistenti? Non è sicuro se l'OP lo desideri.
Peterchen

2
qualcuno sa se c'è qualcosa di simile per unordered_map?
Giovanni Funchal

3
@peterchen map :: insert non sovrascrive il valore esistente se esiste, vedere cplusplus.com/reference/map/map/insert .
Chris Drew

11

La risposta a questa domanda dipende anche da quanto è costoso creare il tipo di valore che stai memorizzando nella mappa:

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

Per un tipo di valore come un int, quanto sopra sarà più efficiente di una ricerca seguita da un inserimento (in assenza di ottimizzazioni del compilatore). Come detto sopra, questo perché la ricerca sulla mappa avviene solo una volta.

Tuttavia, la chiamata a insert richiede che tu abbia già costruito il nuovo "valore":

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

Per chiamare "inserire" stiamo pagando la costosa chiamata per costruire il nostro tipo di valore - e da quanto hai detto nella domanda non utilizzerai questo nuovo valore il 20% delle volte. Nel caso precedente, se la modifica del tipo di valore della mappa non è un'opzione, è più efficiente eseguire prima la "ricerca" per verificare se è necessario costruire l'elemento.

In alternativa, è possibile modificare il tipo di valore della mappa per memorizzare gli handle sui dati utilizzando il tipo di puntatore intelligente preferito. La chiamata a insert utilizza un puntatore nullo (molto economico da costruire) e solo se necessario viene costruito il nuovo tipo di dati.


8

Ci sarà a malapena alcuna differenza di velocità tra i 2, find restituirà un iteratore, insert fa lo stesso e cercherà comunque nella mappa per determinare se la voce esiste già.

Quindi ... dipende dalle preferenze personali. Cerco sempre di inserire e poi aggiornare se necessario, ma ad alcune persone non piace gestire la coppia restituita.


5

Penso che se fai una ricerca e poi inserisci, il costo aggiuntivo sarebbe quando non trovi la chiave ed esegui l'inserimento dopo. È un po 'come guardare i libri in ordine alfabetico e non trovare il libro, quindi sfogliare di nuovo i libri per vedere dove inserirlo. Si riduce a come gestirai le chiavi e se cambiano continuamente. Ora c'è una certa flessibilità in quanto se non lo trovi, puoi accedere, fare eccezione, fare quello che vuoi ...


3

Sono perso sulla risposta in alto.

Trova restituisce map.end () se non trova nulla, il che significa che stai aggiungendo nuove cose

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

è due volte più lento di

map.insert

per qualsiasi elemento non già presente nella mappa poiché dovrà cercare due volte. Una volta per vedere se è lì, di nuovo per trovare il posto dove mettere la nuova cosa.


1
Una versione dell'inserimento STL restituisce una coppia contenente un iteratore e un bool. Il bool indica se l'ha trovato o no, l'iteratore è la voce trovata o la voce inserita. Questo è difficile da battere per l'efficienza; impossibile, direi.
Zan Lynx

4
No, la risposta selezionata è stata utilizzata lower_bound, no find. Di conseguenza, se la chiave non è stata trovata, ha restituito un iteratore al punto di inserimento, non alla fine. Di conseguenza, è più veloce.
Steven Sudit

1

Se sei preoccupato per l'efficienza, potresti voler controllare hash_map <> .

Tipicamente map <> è implementato come un albero binario. A seconda delle tue esigenze, una hash_map potrebbe essere più efficiente.


Mi sarebbe piaciuto. Ma non c'è hash_map nella libreria standard C ++ e PHB non consente il codice al di fuori di questo.
Superpolock

1
std :: tr1 :: unordered_map è la mappa hash che si propone di aggiungere al prossimo standard e dovrebbe essere disponibile nella maggior parte delle attuali implementazioni dell'STL.
beldaz

1

Non mi sembra di avere abbastanza punti per lasciare un commento, ma la risposta spuntata sembra essere prolissa per me - se consideri che l'inserto restituisce comunque l'iteratore, perché andare a cercare lower_bound, quando puoi semplicemente usare l'iteratore restituito. Strano.


1
Poiché (sicuramente prima di C ++ 11) usare insert significa che devi ancora creare un std::map::value_typeoggetto, la risposta accettata evita anche quello.
KillianDS

-1

Qualsiasi risposta sull'efficienza dipenderà dall'esatta implementazione del tuo STL. L'unico modo per saperlo con certezza è confrontarlo in entrambi i modi. Immagino che sia improbabile che la differenza sia significativa, quindi decidi in base allo stile che preferisci.


1
Questo non è esattamente vero. L'STL è diverso dalla maggior parte delle altre librerie in quanto fornisce requisiti espliciti di big-O per la maggior parte delle sue operazioni. Esiste una differenza garantita tra 2 * O (log n) e 1 * O (log n), indipendentemente dall'implementazione utilizzata dalle funzioni per ottenere quel comportamento O (log n). Se questa differenza sia significativa o meno sulla tua piattaforma è una questione diversa. Ma la differenza sarà sempre lì.
srm

@srm che definisce i requisiti big-O ancora non ti dice quanto tempo richiederà un'operazione in termini assoluti. La differenza garantita di cui parli non esiste.
Mark Ransom

-2

map [key] - lascia che stl lo risolva. Questo è comunicare la tua intenzione nel modo più efficace.

Sì, abbastanza giusto.

Se fai una ricerca e poi un inserimento stai eseguendo 2 x O (log N) quando ottieni un errore in quanto la ricerca ti consente solo di sapere se devi inserire non dove dovrebbe andare l'inserto (lower_bound potrebbe aiutarti lì) . Solo un inserto dritto e poi esaminare il risultato è il modo in cui andrei.


No, se la voce esiste, restituisce un riferimento alla voce esistente.
Kris Kumler

2
-1 per questa risposta. Come ha detto Kris K, l'uso di map [key] = value sovrascriverà la voce esistente, non la "preserverà" come richiesto nella domanda. Non puoi testare l'esistenza usando map [key], perché restituirà un oggetto costruito predefinito se la chiave non esiste e lo creerà come voce per la chiave
netjeff

Il punto è verificare se la mappa è già popolata e aggiungere / sovrascrivere solo se non è presente. L'uso di map [key] presuppone che il valore sia già presente sempre.
srm
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.