迭代“在每个连续的元素对之间”之间的习语

79

每个人都会在某个时候遇到这个问题:

for(const auto& item : items) {
    cout << item << separator;
}

... 而且你会在结尾得到一个不想要的额外分隔符。有时它不会打印出来,而是执行其他一些动作,但是这些相同类型的连续动作需要某些分隔符动作 - 但最后一个不需要。

现在,如果您使用旧式for循环和数组,您将执行以下操作

for(int i = 0; i < num_items; i++)
    cout << items[i];
    if (i < num_items - 1) { cout << separator; }
}

(或者您可以将循环中的最后一个项目作为特例处理。)如果您有任何支持非破坏性迭代器的东西,即使您不知道其大小,也可以执行以下操作:

for(auto it = items.cbegin(); it != items.cend(); it++) {
    cout << *it;
    if (std::next(it) != items.cend()) { cout << separator; }
}

我不喜欢最后两种方法的审美效果,更喜欢使用范围for循环。是否可以使用更加时髦的C++11构造实现相同的效果?


为了进一步扩展问题(超出这个问题的范围),我想说我也不想明确地设置第一个或最后一个元素的特殊情况。这是我不想被困扰的“实现细节”。那么,在想象中的未来的C ++中,应该像这样做:

for(const auto& item : items) {
    cout << item;
} and_between {
    cout << separator;
}

5
如果你想借鉴函数式编程的思想,你可以使用 累加算法(accumulate method)。但是这种方法有时候会过于复杂冗长,不一定适用于所有情况。 - Kevin W.
4
C++没有算法级别的“join”。这是非常遗憾的。 - SergeyA
1
@KevinW.,fold 不会帮助你 - 它将对每个元素应用操作,包括第一个和最后一个。 - SergeyA
3
如果你注意到的话,C++ 实现的 accumulate 函数除了连接方法之外还需要三个参数,因此你只需从第二个元素到最后一个元素进行连接,并将第一个元素作为基础元素即可。这种方法完全可以正常工作。事实上,使用分隔符分隔某些内容只是链接操作示例之一,具体请参考链接。 - Kevin W.
1
@SergeyA:我同意,如果你的容器为空,你不能调用std::accumulate,但一个元素应该没问题(这个单独的元素被作为init传递,而且最初begin == end,导致没有折叠)。 - Ben Voigt
显示剩余4条评论
12个回答

3
我不知道"惯用语"的含义,但是C++11为双向迭代器提供了std::prevstd::next函数。
int main() {
    vector<int> items = {0, 1, 2, 3, 4};
    string separator(",");

    // Guard to prevent possible segfault on prev(items.cend())
    if(items.size() > 0) {
        for(auto it = items.cbegin(); it != prev(items.cend()); it++) {
            cout << (*it) << separator;
        }
        cout << (*prev(items.cend()));
    }
}

代码行数太多了,我的朋友 :-) ... 而且我也不想检查 items 的大小。 - einpoklum
2
@einpoklum,我同意你的观点。“最好的代码是你不必写的代码。”我只是想给你一些使用新的C++11高级工具的东西。 - pyj

3

我喜欢boost::join函数。因此,为了获得更一般的行为,您需要一个可以为每对项目调用并具有持久状态的函数。您可以使用带有lambda表达式的函数调用:

foreachpair (range, [](auto left, auto right){ whatever });

现在你可以使用范围过滤器来返回到常规的基于范围的for循环!
for (auto pair : collection|aspairs) {
    Do-something_with (pair.first);
}

在这个想法中,pair被设置为原始集合的一对相邻元素。如果你有"abcde",那么在第一次迭代时,你会得到first='a'和second='b';下一次通过first='b'和second='c';以此类推。
您可以使用类似的过滤器方法来准备一个元组,为每个迭代项标记一个枚举,用于/first/middle/last/迭代,然后在循环内部执行开关。
要简单地省略最后一个元素,请使用除了最后一个元素之外的范围过滤器。我不知道这是否已经在Boost.Range中或者Rangev3正在进行中提供,但这是使常规循环实现技巧并使其“整洁”的一般方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接