Qual è il modo più efficace per ottenere l'indice di un iteratore di uno std :: vector?


439

Sto iterando su un vettore e ho bisogno dell'indice a cui sta puntando l'iteratore. AFAIK questo può essere fatto in due modi:

  • it - vec.begin()
  • std::distance(vec.begin(), it)

Quali sono i pro e i contro di questi metodi?

Risposte:


558

Preferirei it - vec.begin()precisamente per la ragione opposta fornita da Naveen: quindi non si comporterebbe se cambiassi il vettore in un elenco. Se lo fai durante ogni iterazione, potresti facilmente trasformare un algoritmo O (n) in un algoritmo O (n ^ 2).

Un'altra opzione, se non si salta nel contenitore durante l'iterazione, sarebbe quella di mantenere l'indice come contatore del secondo loop.

Nota: itè un nome comune per un iteratore contenitore, std::container_type::iterator it;.


3
Concordato. Direi che il segno meno è il migliore, ma sarebbe meglio mantenere un secondo contatore del loop piuttosto che usare std :: distance, proprio perché questa funzione potrebbe essere lenta.
Steven Sudit,

28
che diamine è it?
Steinfeld,

32
@Steinfeld è un iteratore. std::container_type::iterator it;
Matt Munson,

2
L'aggiunta di un contatore del secondo ciclo è una soluzione così ovvia che sono imbarazzato a non pensarci.
Mordred,

3
@Swapnil perché std::listnon offre accesso diretto agli elementi in base alla loro posizione, quindi se non puoi farlo list[5], non dovresti essere in grado di farlo list.begin() + 5.
José Tomás Tocino,

135

Preferirei std::distance(vec.begin(), it)perché mi permetterà di cambiare il contenitore senza alcuna modifica del codice. Ad esempio, se si decide di utilizzare al std::listposto del std::vectorquale non viene fornito un iteratore ad accesso casuale, il codice verrà comunque compilato. Poiché std :: distance raccoglie il metodo ottimale in base ai tratti dell'iteratore, non si avrà alcun degrado delle prestazioni.


50
Quando si utilizza un contenitore senza iteratori ad accesso casuale, è meglio non calcolare tali distanze perché è inefficiente
Eli Bendersky,

6
@Eli: sono d'accordo, ma in un caso molto speciale se è veramente necessario, allora quel codice funzionerà.
Naveen,

9
Penso che il codice dovrebbe essere cambiato comunque se il contenitore cambia - avere una variabile std :: list chiamata vecè una cattiva notizia. Se il codice è stato riscritto per essere generico, prendendo il tipo di contenitore come parametro modello, è allora che possiamo (e dovremmo) parlare della gestione di iteratori ad accesso non casuale ;-)
Steve Jessop,

1
E specializzazione per determinati contenitori.
ScaryAardvark,

19
@SteveJessop: Avere un vettore chiamato vecè anche una brutta notizia.
Fiume Tam,

74

Come hanno dimostrato UncleBens e Naveen, ci sono buone ragioni per entrambi. Quale è "migliore" dipende dal comportamento che desideri: vuoi garantire un comportamento a tempo costante o vuoi che ritorni al tempo lineare quando necessario?

it - vec.begin()richiede tempo costante, ma operator -è definito solo su iteratori ad accesso casuale, quindi il codice non verrà compilato affatto con iteratori elenco, ad esempio.

std::distance(vec.begin(), it) funziona per tutti i tipi di iteratore, ma sarà un'operazione a tempo costante solo se utilizzato su iteratori ad accesso casuale.

Nessuno dei due è "migliore". Usa quello che fa quello che ti serve.


1
Ho commesso fallo in passato. Usando std :: distance su due iteratori std :: map e aspettandosi che sia O (N).
ScaryAardvark,

6
@ScaryAardvark: non intendi aspettarti che sia O (1)?
jalf

12

Mi piace questo: it - vec.begin()perché per me dice chiaramente "distanza dall'inizio". Con gli iteratori siamo abituati a pensare in termini di aritmetica, quindi il -segno è l'indicatore più chiaro qui.


19
È più chiaro usare la sottrazione per trovare la distanza che usare, letteralmente, la parola distance?
Travis Gockel,

4
@Travis, per me lo è. È una questione di gusti e abitudini. Diciamo it++e non qualcosa del genere std::increment(it), no? Non sarebbe altrettanto meno chiaro?
Eli Bendersky,

3
L' ++operatore è definito come parte delle sequenze STL come come incrementiamo l'iteratore. std::distancecalcola il numero di elementi tra il primo e l'ultimo elemento. Il fatto che l' -operatore funzioni è semplicemente una coincidenza.
Travis Gockel,

3
@MSalters: eppure, usiamo ++ :-)
Eli Bendersky il

10

Se hai già limitato / codificato il tuo algoritmo all'utilizzo di un std::vector::iteratore std::vector::iteratorsolo, non importa quale metodo finirai per utilizzare. Il tuo algoritmo è già concretizzato oltre il punto in cui la scelta dell'altro può fare la differenza. Entrambi fanno esattamente la stessa cosa. È solo una questione di preferenze personali. Personalmente userei la sottrazione esplicita.

Se, d'altra parte, vuoi mantenere un più alto grado di generalità nel tuo algoritmo, vale a dire, per consentire la possibilità che un giorno in futuro possa essere applicato ad un altro tipo di iteratore, allora il metodo migliore dipende dal tuo intento . Dipende da quanto restrittivo vuoi essere rispetto al tipo di iteratore che può essere usato qui.

  • Se usi la sottrazione esplicita, il tuo algoritmo sarà limitato a una classe piuttosto ristretta di iteratori: iteratori ad accesso casuale. (Questo è ciò che ottieni ora std::vector)

  • Se lo usi distance, il tuo algoritmo supporterà una classe molto più ampia di iteratori: input iteratori.

Naturalmente, il calcolo distanceper gli iteratori ad accesso non casuale è in genere un'operazione inefficiente (mentre, ancora una volta, per quelli ad accesso casuale è efficiente quanto la sottrazione). Sta a te decidere se il tuo algoritmo ha senso per iteratori ad accesso non casuale, in termini di efficienza. Se la conseguente perdita di efficienza è devastante al punto da rendere il tuo algoritmo completamente inutile, quindi dovresti attenersi meglio alla sottrazione, vietando così gli usi inefficienti e costringendo l'utente a cercare soluzioni alternative per altri tipi di iteratori. Se l'efficienza con iteratori ad accesso casuale non è ancora nell'intervallo utilizzabile, è necessario utilizzare distancee documentare il fatto che l'algoritmo funziona meglio con iteratori ad accesso casuale.


4

Secondo http://www.cplusplus.com/reference/std/iterator/distance/ , poiché si vec.begin()tratta di un iteratore ad accesso casuale , il metodo della distanza utilizza l' -operatore.

Quindi la risposta è, dal punto di vista delle prestazioni, è la stessa, ma forse usare distance()è più facile da capire se qualcuno dovrebbe leggere e capire il tuo codice.


3

Userei la -variante std::vectorsolo per - è abbastanza chiaro cosa si intende e la semplicità dell'operazione (che non è altro che una sottrazione del puntatore) è espressa dalla sintassi ( distance, dall'altro lato, suona come pitagora sul prima lettura, no?). Come sottolinea UncleBen, -funge anche da asserzione statica nel caso in cui vectorvenga accidentalmente cambiato in list.

Inoltre, penso che sia molto più comune - non ho numeri per dimostrarlo, però. Argomento principale: it - vec.begin()è più breve nel codice sorgente - meno lavoro di battitura, meno spazio consumato. Come è chiaro che la risposta giusta alla tua domanda si riduce a una questione di gusti, questo può anche essere un argomento valido.


0

Ecco un esempio per trovare "tutte" le occorrenze di 10 insieme all'indice. Ho pensato che questo sarebbe stato di qualche aiuto.

void _find_all_test()
{
    vector<int> ints;
    int val;
    while(cin >> val) ints.push_back(val);

    vector<int>::iterator it;
    it = ints.begin();
    int count = ints.size();
    do
    {
        it = find(it,ints.end(), 10);//assuming 10 as search element
        cout << *it << " found at index " << count -(ints.end() - it) << endl;
    }while(++it != ints.end()); 
}
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.