Qual é a maneira mais eficaz de obter o índice de um iterador de um std :: vector?

438

Estou iterando sobre um vetor e preciso do índice que o iterador está apontando no momento. AFAIK, isso pode ser feito de duas maneiras:

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

Quais são os prós e os contras desses métodos?

cairol
fonte

Respostas:

557

Eu preferiria it - vec.begin()precisamente pela razão oposta dada por Naveen: para que não compile se você mudar o vetor em uma lista. Se você fizer isso durante todas as iterações, poderá facilmente transformar um algoritmo O (n) em um algoritmo O (n ^ 2).

Outra opção, se você não pular o contêiner durante a iteração, seria manter o índice como um segundo contador de loop.

Nota: ité um nome comum para um iterador de contêiner std::container_type::iterator it;,.

UncleBens
fonte
3
Acordado. Eu diria que o sinal de menos é o melhor, mas seria melhor manter um segundo contador de loop do que usar std :: distance, precisamente porque essa função pode ser lenta.
Steven Sudit 04/04/19
28
o que diabos é it?
Steinfeld
32
@Steinfeld é um iterador. std::container_type::iterator it;
Matt Munson
2
Adicionar um segundo contador de loop é uma solução tão óbvia que fico envergonhada por não ter pensado nisso.
Mordred
3
@Swapnil porque std::listnão oferece acesso direto aos elementos por sua posição; portanto, se você não puder list[5], não poderá fazê-lo list.begin() + 5.
José Tomás Tocino
135

Eu preferiria std::distance(vec.begin(), it), pois me permitirá alterar o contêiner sem nenhuma alteração no código. Por exemplo, se você decidir usar em std::listvez de std::vectornão fornecer um iterador de acesso aleatório, seu código ainda será compilado. Como o std :: distance escolhe o método ideal, dependendo das características do iterador, você também não terá nenhuma degradação no desempenho.

Naveen
fonte
50
Quando você estiver usando um recipiente sem iteradores de acesso aleatório, é melhor não para computar tais distâncias porque é ineficiente
Eli Bendersky
6
@ Eli: Eu concordo com isso, mas em um caso muito especial, se for realmente necessário, então esse código ainda funcionará.
Naveen
9
Eu acho que o código deve ser alterado de qualquer maneira, se o contêiner for alterado - ter uma variável std :: list chamada vecé uma má notícia. Se o código foi re-escrito para ser genérica, tendo o tipo de recipiente como um parâmetro do modelo, que é quando nós pode (e deve) falar sobre como lidar com iteradores não aleatória de acesso ;-)
Steve Jessop
1
E especialização para determinados contêineres.
ScaryAardvark
19
@SteveJessop: Ter um vetor nomeado também vecé uma notícia muito ruim.
Rio Tam
74

Como o UncleBens e Naveen mostraram, há boas razões para ambos. Qual é "melhor" depende do comportamento que você deseja: deseja garantir o comportamento em tempo constante ou deseja que ele volte ao tempo linear quando necessário?

it - vec.begin()leva tempo constante, mas operator -é definido apenas em iteradores de acesso aleatório; portanto, o código não será compilado com iteradores de lista, por exemplo.

std::distance(vec.begin(), it) funciona para todos os tipos de iteradores, mas somente será uma operação de tempo constante se usado em iteradores de acesso aleatório.

Nenhum dos dois é "melhor". Use aquele que faz o que você precisa.

jalf
fonte
1
Eu me apaixonei por isso no passado. Usando std :: distance em dois iteradores std :: map e esperando que seja O (N).
ScaryAardvark
6
@ ScaryAardvark: Você não quer esperar que seja O (1)?
jalf
12

Eu gosto deste: it - vec.begin()porque para mim diz claramente "distância do começo". Com os iteradores, estamos acostumados a pensar em termos de aritmética, então o -sinal é o indicador mais claro aqui.

Eli Bendersky
fonte
19
É mais claro usar a subtração para encontrar a distância do que usar, literalmente, a palavra distance?
Travis Gockel
4
@ Travis, para mim é. É uma questão de gosto e costume. Dizemos it++e não algo como std::increment(it), não é? Isso também não seria menos claro?
Eli Bendersky
3
O ++operador é definido como parte das sequências STL, como incrementamos o iterador. std::distancecalcula o número de elementos entre o primeiro e o último elemento. O fato de o -operador funcionar é apenas uma coincidência.
Travis Gockel 28/01
3
@MSalters, e ainda, usamos ++ :-)
Eli Bendersky
10

Se você já restringiu / codificou seu algoritmo para usar um std::vector::iteratore std::vector::iteratorsomente, não importa realmente qual método você vai usar. Seu algoritmo já está concretizado além do ponto em que a escolha de um dos outros pode fazer qualquer diferença. Ambos fazem exatamente a mesma coisa. É apenas uma questão de preferência pessoal. Eu pessoalmente usaria subtração explícita.

Se, por outro lado, você deseja manter um maior grau de generalidade em seu algoritmo, ou seja, para permitir a possibilidade de que algum dia no futuro possa ser aplicado a algum outro tipo de iterador, o melhor método depende da sua intenção . Depende de quão restritivo você deseja ser em relação ao tipo de iterador que pode ser usado aqui.

  • Se você usar a subtração explícita, seu algoritmo será restrito a uma classe bastante estreita de iteradores: iteradores de acesso aleatório. (É disso que você obtém agora std::vector)

  • Se você usar distance, seu algoritmo suportará uma classe muito maior de iteradores: iteradores de entrada.

Obviamente, calcular distancepara iteradores de acesso não aleatório é geralmente uma operação ineficiente (enquanto, novamente, para iteradores de acesso aleatório é tão eficiente quanto subtração). Cabe a você decidir se o seu algoritmo faz sentido para iteradores de acesso não aleatório, em termos de eficiência. Como a perda de eficiência resultante é devastadora a ponto de tornar seu algoritmo completamente inútil, é melhor se ater à subtração, proibindo assim os usos ineficientes e forçando o usuário a procurar soluções alternativas para outros tipos de iteradores. Se a eficiência com iteradores de acesso não aleatório ainda estiver no intervalo utilizável, você deve usar distancee documentar o fato de que o algoritmo funciona melhor com iteradores de acesso aleatório.

Formiga
fonte
4

De acordo com http://www.cplusplus.com/reference/std/iterator/distance/ , como vec.begin()é um iterador de acesso aleatório , o método de distância usa o -operador.

Portanto, a resposta é, do ponto de vista do desempenho, a mesma, mas talvez o uso distance()seja mais fácil de entender se alguém precisaria ler e entender seu código.

Stéphane
fonte
3

Eu usaria a -variante std::vectorapenas - é bem claro o que significa, e a simplicidade da operação (que não é mais que uma subtração de ponteiro) é expressa pela sintaxe ( distancedo outro lado, soa como pitágoras no primeira leitura, não é?). Como destaca o UncleBen, -também atua como uma asserção estática, caso vectorseja acidentalmente alterado paralist .

Também acho que é muito mais comum - embora não tenha números para provar. Argumento mestre: it - vec.begin()é mais curto no código fonte - menos trabalho de digitação, menos espaço consumido. Como está claro que a resposta certa para sua pergunta se resume a uma questão de gosto, esse também pode ser um argumento válido.

Alexander Gessler
fonte
0

Aqui está um exemplo para encontrar "todas" ocorrências de 10 junto com o índice. Pensei que isso seria de alguma ajuda.

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()); 
}
Srikanth Batthula
fonte