Quel est le moyen le plus efficace pour obtenir l'index d'un itérateur d'un std :: vector?

439

J'itère sur un vecteur et j'ai besoin de l'index sur lequel l'itérateur pointe actuellement. AFAIK cela peut se faire de deux manières:

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

Quels sont les avantages et les inconvénients de ces méthodes?

cairol
la source

Réponses:

558

Je préférerais it - vec.begin()précisément pour la raison opposée donnée par Naveen: donc ce ne serait pas compilerait donc si vous changez le vecteur en liste. Si vous faites cela à chaque itération, vous pourriez facilement finir par transformer un algorithme O (n) en un algorithme O (n ^ 2).

Une autre option, si vous ne sautez pas dans le conteneur pendant l'itération, serait de conserver l'index comme deuxième compteur de boucle.

Note: itest un nom commun pour un itérateur conteneur, std::container_type::iterator it;.

UncleBens
la source
3
D'accord. Je dirais que le signe moins est le meilleur, mais il serait préférable de garder un deuxième compteur de boucles que d'utiliser std :: distance, précisément parce que cette fonction pourrait être lente.
Steven Sudit
28
qu'est-ce que c'est que ça it?
Steinfeld
32
@Steinfeld est un itérateur. std::container_type::iterator it;
Matt Munson
2
Ajouter un deuxième compteur de boucles est une solution tellement évidente que je suis gêné de ne pas y avoir pensé.
Mordred
3
@Swapnil car std::listn'offre pas d'accès direct aux éléments par leur position, donc si vous ne pouvez pas le faire list[5], vous ne devriez pas pouvoir le faire list.begin() + 5.
José Tomás Tocino
135

Je préférerais std::distance(vec.begin(), it)car cela me permettra de changer le conteneur sans aucun changement de code. Par exemple, si vous décidez d'utiliser std::listau lieu de celui std::vectorqui ne fournit pas d'itérateur d'accès aléatoire, votre code sera toujours compilé. Puisque std :: distance choisit la méthode optimale en fonction des traits de l'itérateur, vous n'aurez pas non plus de dégradation des performances.

Naveen
la source
50
Lorsque vous utilisez un conteneur sans itérateurs d'accès aléatoire, il est préférable de ne pas calculer de telles distances car il est inefficace
Eli Bendersky
6
@Eli: Je suis d'accord avec cela, mais dans un cas très spécial si c'est vraiment nécessaire, alors ce code fonctionnera.
Naveen
9
Je pense que le code devrait être changé de toute façon si le conteneur change - avoir une variable std :: list nommée vecest une mauvaise nouvelle. Si le code a été réécrit pour être générique, en prenant le type de conteneur comme paramètre de modèle, c'est alors que nous pouvons (et devons) parler de la gestion des itérateurs à accès non aléatoire ;-)
Steve Jessop
1
Et spécialisation pour certains conteneurs.
ScaryAardvark
19
@SteveJessop: Avoir un vecteur nommé vecest également une très mauvaise nouvelle.
River Tam
74

Comme l'ont montré UncleBens et Naveen, il y a de bonnes raisons pour les deux. Laquelle est "meilleure" dépend du comportement que vous souhaitez: voulez-vous garantir un comportement à temps constant, ou voulez-vous qu'il revienne au temps linéaire si nécessaire?

it - vec.begin()prend un temps constant, mais operator -n'est défini que sur les itérateurs à accès aléatoire, de sorte que le code ne compilera pas du tout avec les itérateurs de liste, par exemple.

std::distance(vec.begin(), it) fonctionne pour tous les types d'itérateurs, mais ne sera une opération à temps constant que si elle est utilisée sur des itérateurs à accès aléatoire.

Aucun des deux n'est "meilleur". Utilisez celui qui fait ce dont vous avez besoin.

jalf
la source
1
J'en suis tombé dans le passé. Utiliser std :: distance sur deux itérateurs std :: map et s'attendre à ce qu'il soit O (N).
ScaryAardvark
6
@ScaryAardvark: Vous ne voulez pas dire que vous vous attendez à ce que ce soit O (1)?
2010
12

J'aime celui-ci:, it - vec.begin()parce que pour moi, il dit clairement "distance depuis le début". Avec les itérateurs, nous sommes habitués à penser en termes d'arithmétique, donc le -signe est l'indicateur le plus clair ici.

Eli Bendersky
la source
19
Il est plus clair d'utiliser la soustraction pour trouver la distance que d'utiliser, littéralement, le mot distance?
Travis Gockel
4
@Travis, pour moi ça l'est. C'est une question de goût et de coutume. Nous disons it++et pas quelque chose comme std::increment(it)ça, non? Cela ne serait-il pas aussi moins clair?
Eli Bendersky
3
L' ++opérateur est défini dans le cadre des séquences STL comme la façon dont nous incrémentons l'itérateur. std::distancecalcule le nombre d'éléments entre le premier et le dernier élément. Le fait que l' -opérateur travaille n'est qu'une coïncidence.
Travis Gockel
3
@MSalters: et pourtant, nous utilisons ++ :-)
Eli Bendersky
10

Si votre algorithme est déjà limité / codé en dur pour utiliser un std::vector::iteratoret std::vector::iteratorseulement, peu importe la méthode que vous finirez par utiliser. Votre algorithme est déjà concrétisé au-delà du point où le choix de l'un peut faire la différence. Ils font tous les deux exactement la même chose. C'est juste une question de préférence personnelle. J'utiliserais personnellement une soustraction explicite.

Si, d'autre part, vous souhaitez conserver un degré de généralité plus élevé dans votre algorithme, à savoir, pour permettre la possibilité qu'un jour à l'avenir, il puisse être appliqué à un autre type d'itérateur, la meilleure méthode dépend de votre intention. . Cela dépend de la façon dont vous souhaitez être restrictif en ce qui concerne le type d'itérateur qui peut être utilisé ici.

  • Si vous utilisez la soustraction explicite, votre algorithme sera limité à une classe d'itérateurs plutôt étroite: les itérateurs à accès aléatoire. (C'est ce que vous obtenez maintenant std::vector)

  • Si vous utilisez distance, votre algorithme prendra en charge une classe beaucoup plus large d'itérateurs: les itérateurs d'entrée.

Bien entendu, le calcul distancepour les itérateurs à accès non aléatoire est en général une opération inefficace (tandis que, pour les accès aléatoires, il est aussi efficace que la soustraction). C'est à vous de décider si votre algorithme a un sens pour les itérateurs à accès non aléatoire, en termes d'efficacité. Si la perte d'efficacité qui en résulte est dévastatrice au point de rendre votre algorithme complètement inutile, alors vous devriez mieux vous en tenir à la soustraction, interdisant ainsi les utilisations inefficaces et forçant l'utilisateur à rechercher des solutions alternatives pour d'autres types d'itérateurs. Si l'efficacité des itérateurs à accès non aléatoire est toujours dans la plage utilisable, vous devez utiliser distanceet documenter le fait que l'algorithme fonctionne mieux avec les itérateurs à accès aléatoire.

Fourmi
la source
4

Selon http://www.cplusplus.com/reference/std/iterator/distance/ , puisqu'il vec.begin()s'agit d'un itérateur à accès aléatoire , la méthode de la distance utilise l' -opérateur.

Donc, la réponse est, du point de vue des performances, c'est la même chose, mais peut-être que l'utilisation distance()est plus facile à comprendre si quelqu'un doit lire et comprendre votre code.

Stéphane
la source
3

Je n'utiliserais la -variante que pour std::vector- c'est assez clair ce que l'on veut dire, et la simplicité de l'opération (qui n'est pas plus qu'une soustraction de pointeur) est exprimée par la syntaxe ( distance, de l'autre côté, sonne comme pythagore sur le première lecture, non?). Comme le souligne UncleBen, -agit également comme une assertion statique en cas de vectormodification accidentelle enlist .

Je pense aussi que c'est beaucoup plus courant - mais je n'ai pas de chiffres pour le prouver. Argument principal: it - vec.begin()est plus court dans le code source - moins de travail de frappe, moins d'espace consommé. Comme il est clair que la bonne réponse à votre question se résume à une question de goût, cela peut également être un argument valable.

Alexander Gessler
la source
0

Voici un exemple pour trouver "toutes" les occurrences de 10 avec l'index. Je pensais que cela serait d'une certaine aide.

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
la source