简体   繁体   中英

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

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?

Iterating over a linked list is of relatively high cost because of the potential accesses to memory that must be made. You'll want to minimize those accesses. There are a couple things you could do to that end:

  1. Use find_if to search for either 18 or 25
  2. Then search from that point with find_if again for either 7 or the other bound
  3. If the other bound was found 1 st there is no intervening 7 if the 7 was found first ensure the other bound exists

So your code could look like this:

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);

After this if finish doesn't point to cend(vec) then target is a valid pointer to the 1 st 7 in the range.

Live Example


Vlad from Moscow's solution cleverly avoided the need for lambdas by using find_first_of , but it iterated over the contents of vec more than once, making it more expensive than my algorithm. The marriage of these 2 algorithms results in an algorithm that is faster than my original while preserving the benefit of only accessing each element once:

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);

Again if finish doesn't point to cend(vec) then target is a valid pointer to the 1 st 7 in the range.

Live Example

For starters this code snippet (if to update a typo) is wrong

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);

You may not bind a temporary object with a non-constant reference.

As for this approach

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

then you will need to check many conditions. For example before calling

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

you should check whether pos19 is not equal to vec.end() .

As for determining which iterator is less or greater than you can use standard function std::distance . However it is inefficient applied to iterators of the container std::forward_list .

A more efficient approach is to use standard algorithm std::find_first_of instead of the algorithm std::find to find the first iterator of the range.

Here is a demonstrative program

#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;
    }
}

The program output is

7 is between 18 and 25

You don't have any options to compare two ForwardIterator 's other than operator == . This means that you have only two ways here:

  1. Use std::find to ensure the iterators belong to a particular part of your array. This method is implemented in code you cited and in the @Jonathan's answer.
  2. Compare two iterators by writing a special procedure for such operations.

For example, you can write the following code:

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
}

Please also note that this procedure assumes that both iterators belong to the same container and has linear time complexity.

Personally, I would recommend in such situation using a RandomAccessIterator . Yes, std::list does not provide one, but you may use std::vector instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM