How to compare two non-random access iterator in C++

Refresh

December 2018

Views

165 time

1

I have this :

vector<int> vec = {10, 4, 18, 7, 2, 10, 25, 30};
auto& pos25 = find(vec.cbegin(), vec.cend(), 25);
auto& pos18 = find(vec.cbegin(), vec.cend(), 18);

Now, i want to make a query to search for 7 between there two positions. I can just use operator< between pos25 and pos18 since they're random acess iterators and then I can find the location of 7 within that range.

But what if my container is a forward_list. How will I implement that since I don't have an operator< to compare these two iterators; hence I can't know whether pos25 or pos18 occur first to give a range to the find function.

I found this method in a book :

pos18 = find (vec.begin(), vec.end(), // range
18); // value
pos25 = find (vec.begin(), pos18, // range
25); // value
if (pos18 != coll.end() && pos25 != pos18) {
// pos25 is in front of pos18
// so, only [pos25,pos18) is valid
...
}
else {
pos25 = find (pos25, vec.end(), // range
25); // value
if (pos25 != coll.end()) {
// pos18 is in front of pos25
// so, only [pos18,pos25) is valid
...
}
else {
// 18 and/or 25 not found
...
}
}

Though this is simple enough, is there anything more efficient?

3 answers

3

Для начала этот фрагмент кода (если обновить опечатку) неправильно

vector<int> vec = {10, 4, 18, 7, 2, 10, 25, 30};
auto& pos25 = find(vec.cbegin(), vec.cend(), 25);
auto& pos18 = find(vec.cbegin(), vec.cend(), 18);

Вы не можете связать временный объект с непостоянной ссылкой.

Что касается этого подхода

pos18 = find (vec.begin(), vec.end(), // range
18); // value
pos25 = find (vec.begin(), pos18, // range
25); // value

то вам нужно будет проверить много условий. Например перед вызовом

pos25 = find (vec.begin(), pos18, // range
25); // value

вы должны проверить , pos19не равно vec.end().

Что касается определения того, какого итератор меньше или больше , чем вы можете использовать стандартную функцию std::distance. Однако это неэффективно применительно к итераторам контейнера std::forward_list.

Более эффективный подход заключается в использовании стандартного алгоритма std::find_first_ofвместо алгоритма std::findдля поиска первого итератора диапазона.

Вот показательная программа

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main()
{
    std::vector<int> vec = { 10, 4, 18, 7, 2, 10, 25, 30 };
    int a[] = { 25, 18 };

    auto first = std::find_first_of(vec.cbegin(), vec.cend(),
        std::begin(a), std::end(a));

    auto last = vec.cend();
    auto target = vec.cend();

    if (first != vec.cend())
    {
        last = std::find(first, vec.cend(),
            *first == a[0] ? a[1] : a[0]);
    }

    if (last != vec.cend())
    {
        target = std::find(first, last, 7);
    }

    if (target != vec.end())
    {
        std::cout << 7 << " is between " << *first
            << " and " << *last << std::endl;
    }
}

Выход программы

7 is between 18 and 25
3

Перебор связанного списка является относительно высокой стоимости из-за потенциал обращения к памяти, которые должны быть сделаны. Вы хотите, чтобы свести к минимуму этих доступов. Есть несколько вещей, которые вы могли бы сделать с этой целью:

  1. Используйте find_ifдля поиска либо 18 или 25
  2. Тогда поиск с этой точкой с find_ifснова либо 7 или другой оценкой
  3. Если другой связан был найдено 1 - й не вмешиваясь 7 , если 7 было найдено первым обеспечить другую грань существует

Так что ваш код может выглядеть следующим образом:

const auto start = find_if(cbegin(vec), cend(vec), [](const auto& i){ return i == 18 || i == 25; });
const auto target = find_if(start, cend(vec), [finish = *start == 18 ? 25 : 18](const auto& i){ return i == 7 || i == finish; });
const auto finish = *target == 7 ? find(target, cend(vec), *start == 18 ? 25 : 18) : cend(vec);

После этого , если finishне указывает на cend(vec)то targetдействительный указатель на 1 - й 7 в диапазоне.

Live Example


Влад из решения Москвы хитро избежать необходимости лямбды с помощи find_first_of, но итерация над содержимым vecболее чем один раз, что делает его более дорогим , чем мой алгоритм. Брак этих 2 -х алгоритмов приводит к алгоритму , который быстрее , чем мой оригинал, сохраняя преимущество только доступ к каждому элементу один раз:

const int a[] = { 18, 25 };
const auto start = find_first_of(cbegin(vec), cend(vec), cbegin(a), cend(a));
const int b[] = { *start == *cbegin(a) ? *crbegin(a) : *cbegin(a), 7 };
const auto target = find_first_of(start, cend(vec), cbegin(b), cend(b));
const auto finish = *target == *crbegin(b) ? find(target, cend(vec), *cbegin(b)) : cend(vec);

Опять же, если finishне указывает на cend(vec)то targetдействительный указатель на 1 - й 7 в диапазоне.

Live Example

0

У вас нет никаких вариантов для сравнения два ForwardIteratorотличных «S operator ==. Это означает , что у вас есть только два пути здесь:

  1. Используйте std::findдля обеспечения итераторы принадлежат к определенной части вашего массива. Этот метод реализован в коде вы цитируемой и в @ ответ Джонатана.
  2. Сравните два итератора, написав специальную процедуру для таких операций.

Например, вы можете написать следующий код:

template<typename ForwardIterator>
bool less(ForwardIterator lhs, ForwardIterator rhs, ForwardIterator end) {
    if (lhs == rhs) {
        return false; // Equal
    }
    while (lhs++ != end) {
        if (lhs == rhs) {
            return true; // rhs is after lhs
        }
    }
    return false; // lhs is after rhs
}

Обратите внимание, что эта процедура предполагает, что оба итератора принадлежит к одной и той же емкости и имеет линейную временную сложность.

Лично я рекомендовал бы в такой ситуации , используя RandomAccessIterator. Да, его std::listне существует, но вы можете использовать std::vectorвместо этого.