如何使用标准库迭代相等的值?

时间:2019-07-02 20:38:27

标签: c++ algorithm c++17 c++-standard-library iterator-range

假设我有一个向量:

std::vector<Foo> v;

此向量已排序,因此相等的元素彼此相邻。

获取所有表示具有相等元素的范围的迭代器对的最佳方法是什么(使用标准库)?

while (v-is-not-processed) {
    iterator b = <begin-of-next-range-of-equal-elements>;
    iterator e = <end-of-next-range-of-equal-elements>;

    for (iterator i=b; i!=e; ++i) {
        // Do something with i
    }
}

我想知道如何在上面的代码中获取be的值。

例如,如果v包含以下数字:

 index 0 1 2 3 4 5 6 7 8 9
 value 2 2 2 4 6 6 7 7 7 8

然后我想让be指向循环中的元素:

 iteration  b  e
 1st        0  3
 2nd        3  4
 3rd        4  6
 4th        6  9
 5th        9 10

是否可以使用标准库解决此问题?

6 个答案:

答案 0 :(得分:25)

您可以使用std::upper_bound将迭代器设置为“ next”值。由于std::upper_bound将第一个元素返回的迭代器大于所提供的值,因此,如果您提供当前元素的值,它将为您提供一个迭代器,该迭代器将比当前值的末尾晚一个。那会给你一个像

的循环
iterator it = v.begin();
while (it != v.end()) {
    iterator b = it;
    iterator e = std::upper_bound(it, v.end(), *it);

    for (iterator i=b; i!=e; ++i) {
        // do something with i
    }
    it = e; // need this so the loop starts on the next value
}

答案 1 :(得分:24)

这基本上是Range v3的group_bygroup_by(v, std::equal_to{})。它在C ++ 17标准库中不存在,但是我们可以编写自己的粗略等价物:

template <typename FwdIter, typename BinaryPred, typename ForEach>
void for_each_equal_range(FwdIter first, FwdIter last, BinaryPred is_equal, ForEach f) {
    while (first != last) {
        auto next_unequal = std::find_if_not(std::next(first), last,
            [&] (auto const& element) { return is_equal(*first, element); });

        f(first, next_unequal);
        first = next_unequal;
    }
}

用法:

for_each_equal_range(v.begin(), v.end(), std::equal_to{}, [&] (auto first, auto last) {
    for (; first != last; ++first) {
        // Do something with each element.
    }
});

答案 2 :(得分:17)

您正在寻找std::equal_range

  

返回一个范围,其中包含与   范围 [第一个,最后一个)

类似以下的方法应该起作用。

mysql -u username -p -h host.domain.edu -P 1234

备注:即使这是一种直观的方法,config/database.php也会获得其 first second 迭代器(即auto it = v.begin(); while (it != v.end()) { auto [b, e] = std::equal_range(it, v.end(), *it); for (; b != e; ++b) { /* do something in the range[b, e) */ } it = e; // need for the beginning of next std::equal_range } std::equal_range)借助于std::lower_boundstd::upper_bound的使用,这使方法slightly inefficient得到了解决。由于 first 迭代器在OP的情况下很容易访问,因此只需要调用b进行 second 迭代器(如 @NathanOliver 的答案)。

答案 3 :(得分:8)

如果您的相等值范围较短,那么std::adjacent_find会很好地工作:

struct

如果您愿意,也可以用λfor (auto it = v.begin(); it != v.end();) { auto next = std::adjacent_find(it, v.end(), std::not_equal_to<Foo>()); for(; it != next; ++it) { } } 代替。

答案 4 :(得分:7)

  

但是,即使我们什么都不使用e,这种表达方式还是很方便的,但更容易出错。另一种方法(检查值的更改)比较繁琐(因为我们需要专门处理最后一个范围)[...]

取决于您如何理解“特别处理最后一个范围”

auto begin = v.begin();
// we might need some initialization for whatever on *begin...
for(Iterator i = begin + 1; ; ++i)
{
    if(i == v.end() || *i != *begin)
    {
        // handle range single element of range [begin, ???);
        if(i == v.end())
            break;
        begin = i;
        // re-initialize next range
    }
}

对最后一个范围没有特殊处理-仅可能需要两次初始化代码...

嵌套循环法:

auto begin = v.begin();
for(;;)
{
    // initialize first/next range using *begin
    for(Iterator i = begin + 1; ; ++i)
    {
        if(i == v.end() || *i != *begin)
        {
            // handle range single element of range [begin, ???);
            if(i == v.end())
                goto LOOP_EXIT;
            begin = i;
            break;
        }
    }
}
LOOP_EXIT:
// go on
// if nothing left to do in function, we might prefer returning over going to...

更优雅?诚然,我自己对此表示怀疑……但这两种方法都避免在相同的范围内两次迭代(首先是找到终点,然后是实际迭代)。如果我们通过以下方式创建自己的库函数:

template <typename Iterator, typename RangeInitializer, typename ElementHandler>
void iterateOverEqualRanges
(
    Iterator begin, Iterator end,
    RangeInitializer ri, ElementHandler eh
)
{
    // the one of the two approaches you like better
    // or your own variation of...
}

然后我们可以像这样使用它:

std::vector<...> v;
iterateOverEqualRanges
(
    v.begin(), v.end(),
    [] (auto begin) { /* ... */ },
    [] (auto current) { /* ... */ }
);

现在,终于看起来像e。 G。 std::for_each,不是吗?

答案 5 :(得分:0)

for(auto b=v.begin(), i=b, e=v.end(); i!=e; b=i) {
    // initialise the 'Do something' code for another range
    for(; i!=e && *i==*b; ++i) {
        // Do something with i
    }
}