Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/152.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 如何使用标准库迭代相等的值?_C++_Algorithm_C++17_C++ Standard Library_Iterator Range - Fatal编程技术网

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

C++ 如何使用标准库迭代相等的值?,c++,algorithm,c++17,c++-standard-library,iterator-range,C++,Algorithm,C++17,C++ Standard Library,Iterator Range,假设我有一个向量: std::vector<Foo> v; 然后我想让b和e指向循环中的元素: iteration b e 1st 0 3 2nd 3 4 3rd 4 6 4th 6 9 5th 9 10 使用标准库是否有一种优雅的方法来解决这个问题?您可以使用它将迭代器设置为“下一个”值。由于std::upper_bound返回一个迭代器到大于所提供值的第一个元素,如果您提供当前元素的

假设我有一个向量:

std::vector<Foo> v;
然后我想让
b
e
指向循环中的元素:

 iteration  b  e
 1st        0  3
 2nd        3  4
 3rd        4  6
 4th        6  9
 5th        9 10
使用标准库是否有一种优雅的方法来解决这个问题?

您可以使用它将迭代器设置为“下一个”值。由于
std::upper_bound
返回一个迭代器到大于所提供值的第一个元素,如果您提供当前元素的值,它将给您一个迭代器,该迭代器将超过当前值的末尾。这会给你一个像这样的循环

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

这基本上是v3的范围:
group_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;
    }
}
你在找我

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

像下面这样的方法应该可以奏效

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
获得其第一个和第二个迭代器(即
b
e
)在and的帮助下,这使得这一方法成为可能。因为,对于OP的情况,第一个迭代器可以很容易地访问,只需要为第二个迭代器调用
std::upper_bound
(如@NathanOliver的回答所示)

但是,即使我们什么都不使用e,这个公式也很方便,更难出错。另一种方法(检查值的变化)更繁琐(因为我们需要特别处理最后一个范围[…])

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

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

嵌套循环方法:

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...
}
模板
void iterateOverEqualRanges
(
迭代器开始,迭代器结束,
范围初始值设定项ri,ElementHandler eh
)
{
//您更喜欢的两种方法之一
//或者你自己的。。。
}
然后我们可以像这样使用它:

std::vector<...> v;
iterateOverEqualRanges
(
    v.begin(), v.end(),
    [] (auto begin) { /* ... */ },
    [] (auto current) { /* ... */ }
);
std::vector v;
迭代等式范围
(
v、 开始(),v.结束(),
[](自动开始){/*…*/},
[](自动电流){/*…*/}
);

最后,它看起来与例如std::for_each,类似,不是吗?

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

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

    }
}
for(自动it=v.begin();it!=v.end();){
自动下一步=标准::相邻查找(它,v.end(),标准::不等于();
for(;it!=next;++it){
}
}

如果你愿意,你也可以用lambda代替
std::not_equal_to

如果你知道这些相等元素的子范围很大,你可以通过探索某种二进制搜索来获益。我最喜欢这个解决方案,因为它的用法是最清晰的。唯一的问题是
std::upper_bound
有一点more,因为它必须使用二进制搜索来查找元素。但在我的情况下,这是不必要的。如果相等元素的子范围较大,这是有意义的。如果它们较小,则在整个范围内进行二进制搜索会浪费精力,而线性搜索可以更快地找到下一个元素(并且具有更好的缓存位置)@geza如果你想做一个线性遍历,那么你可以用
std::upper_-bound(it,v.end(),*it);
替换为
std::find_-If(it,v.end(),[=](auto e){return*it!=e;};
。根据数据的不同,这肯定会更快。+1,谢谢,但我接受了Justin的解决方案,因为它的用法更清楚一点(我的意思是,该算法有一个名称,因此更容易理解代码的作用-但当然,您的变体也可以通过这种方式修改,您的解决方案基本相同)。应该注意的是,问题中的代码并不是一个很好的例子,说明了这是如何有用的。
e
除了限制内环外,什么也做不了,内环也可以通过测试元素
i
中的新值来限制。因此,花费在寻找
e
上的任何努力都是没有用的(除非用于排序
v
的“值”的计算过于昂贵,以至于对
e
进行二进制搜索要比在进行时测试每个元素便宜)。@EricPostPrischil:这是真的。但即使我们对任何东西都不使用
e
,这个公式也很方便,很难出错。相反(检查值的变化)更繁琐(因为我们需要特别处理最后一个范围-或者你知道一些避免它的技巧吗?)@geza最后一个范围不需要特殊处理。当我们知道它只是
it
时,这会做一些额外的工作来找到范围的下限,但在这一点上,我们将与NathanOliver的答案相同(
std::upper_bound
而不是
std::equal_range
).@Justin同意。也许有一点好处:由于可以进行结构化绑定,因此可以减少键入。+1,但我接受了Justin的解决方案,因为这是最短的版本(而且不需要添加名称也很容易理解),通过
std::equal_range
完成了不必要的工作。使用
std::nexting_find
找到边界的好技巧!感谢解决方案,我喜欢它不需要对元素进行双重迭代。通过“特别处理最后一个范围”我的意思是我们需要以某种方式检查它。即使是通过做两次
I==v.end()
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
    }
}
std::vector<...> v;
iterateOverEqualRanges
(
    v.begin(), v.end(),
    [] (auto begin) { /* ... */ },
    [] (auto current) { /* ... */ }
);
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) {

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