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

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

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

我不喜欢最后两个的美学,喜欢循环的范围。我可以获得与最后两个相同的效果,但使用更漂亮的 C + + 11ish 构造?


为了进一步扩展这个问题(比如说,超越 这个) ,我想说我也不想明确地将第一个或最后一个元素特殊化。这是一个“实现细节”,我不想被打扰。所以,在虚拟的-未来-C + + 中,可能是这样的:

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

我不知道有什么特别的成语。但是,我倾向于先特殊处理,然后再对其余的项目执行操作。

#include <iostream>
#include <vector>


int main()
{
std::vector<int> values = { 1, 2, 3, 4, 5 };


std::cout << "\"";
if (!values.empty())
{
std::cout << values[0];


for (size_t i = 1; i < values.size(); ++i)
{
std::cout << ", " << values[i];
}
}
std::cout << "\"\n";


return 0;
}

输出: "1, 2, 3, 4, 5"

通常我会反其道而行之:

bool first=true;
for(const auto& item : items) {
if(!first) cout<<separator;
first = false;
cout << item;
}

从迭代中排除一个最终元素是 Range 提案设计的目的是为了简化的事情。(注意,有更好的方法来解决字符串连接的特定任务,从迭代中断开一个元素只会创建更多需要担心的特殊情况,例如当集合已经为空时。)

当我们等待标准化的 Range 范例时,我们可以使用现有的 range-for 和一个小的 helper 类来完成。

template<typename T> struct trim_last
{
T& inner;


friend auto begin( const trim_last& outer )
{ using std::begin;
return begin(outer.inner); }


friend auto end( const trim_last& outer )
{ using std::end;
auto e = end(outer.inner); if(e != begin(outer)) --e; return e; }
};


template<typename T> trim_last<T> skip_last( T& inner ) { return { inner }; }

现在你可以写作了

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

演示: http://rextester.com/MFH77611

对于使用 range-For 的 skip_last,需要一个双向迭代器,对于类似的 skip_first,有一个 Forward 迭代器就足够了。

我的方法(没有额外的分支)是:

const auto separator = "WhatYouWantHere";
const auto* sep = "";
for(const auto& item : items) {
std::cout << sep << item;
sep = separator;
}

我不知道什么是“惯用”,但是 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()));
}
}

我不认为你可以绕过一个特殊情况 某个地方... 例如,Boost 的 字符串算法库有一个 加入算法。如果查看它的 实施,您将看到第一个项的特殊情况(没有前进的分隔符) ,并且在每个后续元素之前添加一个 then 分隔符。

你知道 达夫的装置吗?

int main() {
int const items[] = {21, 42, 63};
int const * item = items;
int const * const end = items + sizeof(items) / sizeof(items[0]);
// the device:
switch (1) {
case 0: do { cout << ", ";
default: cout << *item; ++item; } while (item != end);
}


cout << endl << "I'm so sorry" << endl;
return 0;
}

(现场直播)

希望我没有毁了大家的一天。如果你不想,那么 永远不会使用这个。

我很抱歉..。


处理空容器的设备(范围) :

template<typename Iterator, typename Fn1, typename Fn2>
void for_the_device(Iterator from, Iterator to, Fn1 always, Fn2 butFirst) {
switch ((from == to) ? 1 : 2) {
case 0:
do {
butFirst(*from);
case 2:
always(*from); ++from;
} while (from != to);
default: // reached directly when from == to
break;
}
}

现场测试 :

int main() {
int const items[] = {21, 42, 63};
int const * const end = items + sizeof(items) / sizeof(items[0]);
for_the_device(items, end,
[](auto const & i) { cout << i;},
[](auto const & i) { cout << ", ";});
cout << endl << "I'm (still) so sorry" << endl;
// Now on an empty range
for_the_device(end, end,
[](auto const & i) { cout << i;},
[](auto const & i) { cout << ", ";});
cout << "Incredibly sorry." << endl;
return 0;
}

我喜欢简单的控制结构。

if (first == last) return;


while (true) {
std::cout << *first;
++first;
if (first == last) break;
std::cout << separator;
}

根据您的口味,您可以将增量和测试放在一行中:

...
while (true) {
std::cout << *first;
if (++first == last) break;
std::cout << separator;
}

您可以为 _ each _ and _ join 定义一个以两个函数为参数的函数。第一个函数处理每个元素,第二个函数处理每对相邻的元素:

#include <iostream>
#include <vector>


template <typename Iter, typename FEach, typename FJoin>
void for_each_and_join(Iter iter, Iter end, FEach&& feach, FJoin&& fjoin)
{
if (iter == end)
return;


while (true) {
feach(*iter);
Iter curr = iter;
if (++iter == end)
return;
fjoin(*curr, *iter);
}
}


int main() {
std::vector<int> values = { 1, 2, 3, 4, 5 };
for_each_and_join(values.begin(), values.end()
,  [](auto v) { std::cout << v; }
,  [](auto, auto) { std::cout << ","; }
);
}

实例: http://ideone.com/fR5S9H

int a[3] = {1,2,3};
int size = 3;
int i = 0;


do {
std::cout << a[i];
} while (++i < size && std::cout << ", ");

产出:

1, 2, 3

目标是使用评估 &&的方法。如果第一个条件为 true,则计算第二个条件。如果不为真,则跳过第二个条件。

下面是我喜欢使用的一个小技巧:

对于双向迭代的对象: For (auto it = items.start () ; it! = items.end () ; it + +) { std: : cout < < * it < (it = = items.end ()-1? “ : sep) ; } ;

使用三元 ?操作符,我将迭代器的当前位置与 item.end()-1调用进行比较。由于 item.end()返回的迭代器指向位置 之后的最后一个元素,因此我们减去它一次以获得实际的最后一个元素。

如果这个条目不是迭代中的最后一个元素,我们返回分隔符(在其他地方定义) ,或者如果它是最后一个元素 ,我们返回一个空字符串。

对于单向迭代器(使用 std: : forward _ list 测试) : For (auto it = items.start () ; it! = items.end () ; it + +) { std: : cout < < * it < (std: : length (it,items.end ()) = = 1? “ : sep) ; } ;

在这里,我们使用当前的迭代器位置和迭代器的末尾来替换前面的三元条件,调用 std: : far。

注意,这个版本既可以使用双向迭代,也可以使用单向迭代。

编辑: 我知道您不喜欢 .begin().end()类型的迭代,但是如果您希望保持 LOC 计数,那么在这种情况下您可能必须避免基于范围的迭代。

如果您的比较逻辑相对简单,那么“技巧”只是将比较逻辑包装在单个三元表达式中。

我喜欢 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/mid/last/迭代的枚举标记每个迭代项,然后在循环中执行切换。

为了简单地省略最后一个元素,可以使用范围过滤器来过滤除了最后一个元素之外的所有元素。我不知道那是不是已经在 Boost 了。Range 或者 Rangev3中可能提供的内容,但是这是使常规循环做技巧并使其“整洁”的一般方法。