Який найефективніший спосіб отримати індекс ітератора std :: vector?


439

Я повторюю над вектором і мені потрібен індекс, на який вказує ітератор. AFAIK це можна зробити двома способами:

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

Які плюси і мінуси цих методів?

Відповіді:


558

Я вважаю it - vec.begin()за краще саме з протилежної причини, яку подав Naveen: тому він не складеться, якщо ви переймете вектор у список. Якщо ви робите це під час кожної ітерації, ви можете легко перетворити алгоритм O (n) в алгоритм O (n ^ 2).

Інший варіант, якщо ви не стрибаєте в контейнері під час ітерації, - це зберігати індекс як лічильник другого циклу.

Примітка: itце загальна назва для контейнера ітератора std::container_type::iterator it;.


3
Домовились. Я б сказав, що знак мінус найкращий, але було б краще тримати лічильник другого циклу, ніж використовувати std :: distance саме тому, що ця функція може бути повільною.
Стівен Судіт

28
що за чорт it?
Штейнфельд

32
@Steinfeld - її ітератор. std::container_type::iterator it;
Метт Мунсон

2
Додавання другого лічильника циклу - це настільки очевидне рішення, що я збентежений, що не думав про це.
Мордред

3
@Swapnil тому, std::listщо не надає прямий доступ до елементів за їхньою позицією, тому, якщо ви не можете цього зробити list[5], ви не зможете це зробити list.begin() + 5.
Хосе Томаш Точіно

135

Я вважаю за краще, std::distance(vec.begin(), it)оскільки це дозволить мені змінити контейнер без будь-яких змін коду. Наприклад, якщо ви вирішили використовувати std::listзамість std::vectorякого не передбачений ітератор випадкового доступу, ваш код все одно буде компілюватися. Оскільки std :: distance підбирає оптимальний метод залежно від рис ітератора, ви також не будете погіршувати продуктивність.


50
Коли ви використовуєте контейнер без ітераторів випадкового доступу, краще не обчислювати такі відстані, оскільки це неефективно
Елі Бендерський,

6
@Eli: Я згоден з цим, але в дуже особливому випадку, якщо це дійсно потрібно, то все-таки цей код буде працювати.
Naveen

9
Я думаю, що код у будь-якому випадку слід змінити, якщо контейнер зміниться - із зазначенням змінної std :: list vec- це погані новини. Якщо код був переписаний на загальний характер, приймаючи тип контейнера як параметр шаблону, тоді ми можемо (і повинні) говорити про обробку ітераторів без випадкового доступу ;-)
Стів Джессоп

1
І спеціалізація для певних контейнерів.
ScaryAardvark

19
@SteveJessop: Вектор з ім'ям vecтеж погана новина.
Річка Там

74

Як показали UncleBens та Naveen, для обох є вагомі причини. Який з них "кращий" залежить від того, яку поведінку ви хочете: Ви хочете гарантувати поведінку постійного часу, чи ви хочете, щоб вона повернулася до лінійного часу, коли це необхідно?

it - vec.begin()займає постійний час, але operator -визначається лише на ітераторах випадкового доступу, тому код взагалі не компілюється із ітераторами списку.

std::distance(vec.begin(), it) працює для всіх типів ітераторів, але буде операцією постійного часу лише у разі використання ітераторів випадкового доступу.

Ні один не "кращий". Використовуйте той, що робить те, що вам потрібно.


1
Я пропадав у цьому в минулому. Використовуючи std :: відстань на двох std :: ітераторах карти та очікуючи, що це буде O (N).
ScaryAardvark

6
@ScaryAardvark: Ви не маєте на увазі очікування, що це буде O (1)?
jalf

12

Мені це подобається: it - vec.begin()адже мені чітко сказано "відстань від початку". З ітераторами ми звикли думати з точки зору арифметики, тому -знак є найяснішим показником тут.


19
Більш зрозуміло використовувати віднімання, щоб знайти відстань, ніж використовувати, буквально, слово distance?
Травіс Гокель

4
@Travis, для мене це так. Це питання смаку та звичаю. Ми говоримо, it++а не щось на кшталт std::increment(it), чи не так? Чи не вважатиметься це менш зрозумілим?
Елі Бендерський

3
++Оператор визначається як частина STL послідовностей , як , як ми збільшуємо итератор. std::distanceобчислює кількість елементів між першим і останнім елементом. Те, що -оператор працює - це лише збіг обставин.
Тревіс Гоккель

3
@MSalters: і все ж ми використовуємо ++ :-)
Елі Бендерський,

10

Якщо ви вже обмежили / жорстко закодували свій алгоритм на використання єдиного std::vector::iteratorі std::vector::iteratorєдиного, це не має значення, який метод ви в кінцевому підсумку будете використовувати. Ваш алгоритм вже конкретизований поза межами тієї точки, коли вибір одного з інших може змінити будь-яке значення. Вони обидва роблять точно те саме. Це лише питання особистої переваги. Я особисто використовував би чітке віднімання.

Якщо, з іншого боку, ви хочете зберегти більш високий ступінь загальності у своєму алгоритмі, а саме, щоб дозволити можливість того, що якийсь день у майбутньому він може бути застосований до якогось іншого типу ітератора, то найкращий метод залежить від вашого наміру . Це залежить від того, наскільки ви хочете бути обмеженими щодо типу ітератора, який можна тут використовувати.

  • Якщо ви використовуєте явне віднімання, ваш алгоритм буде обмежений досить вузьким класом ітераторів: ітераторів з випадковим доступом. (Це те, що ви отримуєте зараз від std::vector)

  • Якщо ви використовуєте distance, ваш алгоритм буде підтримувати набагато ширший клас ітераторів: ітератори введення.

Звичайно, обчислення distanceдля ітераторів без випадкового доступу, як правило, є неефективною операцією (в той час, як, для випадкового доступу, це так само ефективно, як і віднімання). Ви вирішуєте, чи має ваш алгоритм сенс для ітераторів без випадкового доступу, залежно від ефективності. Тому що втрата ефективності нищівна до того, що робить ваш алгоритм абсолютно марним, тоді вам слід краще дотримуватися віднімання, забороняючи таким чином неефективне використання і змушуючи користувача шукати альтернативні рішення для інших типів ітераторів. Якщо ефективність роботи з ітераторами без випадкового доступу все ще знаходиться в застосуваному діапазоні, то слід використовувати distanceта документувати той факт, що алгоритм краще працює з ітераторами випадкового доступу.


4

Відповідно до http://www.cplusplus.com/reference/std/iterator/distance/ , оскільки vec.begin()це ітератор випадкового доступу , метод дистанції використовує -оператор.

Тож відповідь з точки зору продуктивності - однакова, але, можливо, використання distance()легше зрозуміти, чи комусь доведеться читати та розуміти ваш код.


3

Я б застосував -варіант std::vectorлише для цього - досить зрозуміло, що мається на увазі, а простота операції (яка не більше ніж віднімання вказівника) виражається синтаксисом ( distanceз іншого боку, це звучить як піфагор на перше читання, чи не так?). Як вказує UncleBen, він -також виступає як статичне твердження у випадку, якщо воно vectorбуде офіційно змінено list.

Крім того, я думаю, що це набагато частіше - все ж немає цифр, щоб довести це. Головний аргумент: it - vec.begin()коротший вихідний код - менше набору тексту, менше зайнятого місця. Оскільки зрозуміло, що правильна відповідь на ваше запитання зводиться до смаку, це також може бути вагомим аргументом.


0

Ось приклад для пошуку "всіх" подій 10 разом з індексом. Думав, що це допоможе.

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()); 
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.