【问题标题】:How to compare two non-random access iterator in C++如何在 C++ 中比较两个非随机访问迭代器
【发布时间】:2017-04-29 18:55:27
【问题描述】:

我有这个:

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

现在,我想查询两个位置之间的 7。我可以在pos25pos18 之间使用operator&lt;,因为它们是随机访问迭代器,然后我可以find 在该范围内找到7 的位置。

但是如果我的容器是forward_list。我将如何实现它,因为我没有 operator&lt; 来比较这两个迭代器;因此我不知道是 pos25 还是 pos18 首先出现给 find 函数一个范围。

我在一本书中找到了这个方法:

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
...
}
}

虽然这很简单,但还有什么更高效的吗?

【问题讨论】:

  • 这很繁琐,但你不会为了效率而打败它——它通过搜索整个向量找到pos18,然后通过首先从begin到@搜索找到pos25 987654336@,然后从pos18end - 所以它是最优的。我认为你不能做得更好。
  • @MartinBonner 这会不止一次地查看元素,因此您可以绝对击败它以提高效率,请参阅my answer
  • @JonathanMee:啊哈!我是根据“比较次数”而不是“迭代次数”来考虑效率的——我通常认为它“便宜”——但你是对的。在链表的情况下,这是一种糟糕的直觉。

标签: c++ c++11 stl iterator find


【解决方案1】:

迭代链表的成本相对较高,因为必须进行对内存的潜在访问。您将希望最小化这些访问。为此,您可以做几件事:

  1. 使用find_if 搜索1825
  2. 然后使用find_if 再次搜索该点以查找7 或另一个边界
  3. 如果在第 1st 找到另一个边界,则没有干预 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 是指向范围内第 1st7 的有效指针。

Live Example


Vlad from Moscow's solution 通过使用find_first_of 巧妙地避免了对 lambdas 的需求,但它不止一次地迭代了vec 的内容,这使得它比我的算法更昂贵。这两种算法的结合产生了一种比我原来的算法更快的算法,同时保留了每个元素只访问一次的好处:

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 是指向范围内第一个st 7 的有效指针。

Live Example

【讨论】:

    【解决方案2】:

    对于初学者,此代码 sn-p(如果要更新错字)是错误的

    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
    

    【讨论】:

    • 使用a 来避免对 lambda 的需求是聪明的 +1。但是这个算法不止一次地迭代vec 的元素,这是不必要的。结合您和我的想法的答案可能是最好的:)
    • @JonathanMee 我不明白你的意思是说该算法不止一次地迭代 vec 的元素。
    • 我已更新 my answer 以结合我们的算法,但为了解释的完整性,请注意我的解决方案的 target 开始于 find_first_of 范围 startfinish 开始于 @987654339 @ range at target,所以从来没有一个元素被检查过一次以上。
    【解决方案3】:

    除了operator == 之外,您没有任何选项可以比较两个ForwardIterator。这意味着您在这里只有两种方式:

    1. 使用std::find 确保迭代器属于数组的特定部分。此方法在您引用的代码和@Jonathan 的答案中实现。
    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

    【讨论】:

    • A list 没有提供“RandomAccessIterator”...和“吃仙人掌”是认真的吗?我认为删除此类风味文字会改善您的答案。
    • @JonathanMee 已修复
    猜你喜欢
    • 2022-01-04
    • 2015-05-13
    • 1970-01-01
    • 1970-01-01
    • 2021-12-01
    • 2012-03-03
    • 1970-01-01
    • 2022-11-14
    • 2016-10-16
    相关资源
    最近更新 更多