“除了最后一个”(或“在每一对连续的元素之间”)的习语(重复)
大家在某个时候遇到这个问题:
for(const auto& item : items) { cout << item << separator; }
…你最后得到一个你不想要的额外的分隔符。 有时不是打印,而是执行一些其他操作,但是这种连续的相同types的操作需要一些分隔符操作 – 但最后一个操作不会。
现在,如果你使用老式的循环和数组,你会这样做
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; }
我的方式(没有额外的分支)是:
const auto separator = "WhatYouWantHere"; const auto* sep = ""; for(const auto& item : items) { std::cout << sep << item; sep = separator; }
你知道达夫的装置吗?
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; }
(生活)
希望我没有毁了每个人的一天。 如果你不想要,那么永远不要使用这个。
(咕I)我很抱歉…
处理空容器(范围)的设备:
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; } }
现场testing :
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; }
从迭代中排除末尾元素是Ranges提议旨在简化的事情。 (请注意,有更好的方法来解决string连接的具体任务,将元素从迭代中分离出来只会产生更多特殊情况,比如集合已经空了。
当我们等待一个标准化的范围范例时,我们可以用现有的范围内的帮助类来做。
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
对于使用ranged-for的skip_last
,需要一个双向迭代器,对于类似的skip_first
,只需要一个Forward迭代器即可。
我不知道这个有什么特别的成语。 但是,我更喜欢首先特殊情况,然后对其余项目执行操作。
#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; }
我喜欢简单的控制结构。
if (first == last) return; while (true) { std::cout << *first; ++first; if (first == last) break; std::cout << separator; }
根据您的口味,您可以将增量和testing放在一行中:
... while (true) { std::cout << *first; if (++first == last) break; std::cout << separator; }
我没有东西可以解决一些特殊情况…例如,Boost的stringalgorithm库有一个连接algorithm。 如果你看看它的实现 ,你会看到第一个项目的特殊情况(没有继续分隔符),然后在每个后续元素之前添加一个分隔符。
你可以定义一个函数for_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
目标是使用&&
评估的方式。 如果第一个条件成立,则评估第二个条件。 如果不是这样,则跳过第二个条件。
我不知道“惯用”,但是C ++ 11为双向迭代器提供了std::prev
和std::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::join
函数。 所以对于更一般的行为,你需要为每对项目调用一个函数,并且可以有一个持久状态。 你可以使用它作为lambda的函数调用:
foreachpair (range, [](auto left, auto right){ whatever });
现在,您可以使用范围filter恢复到基于范围的常规循环。
for (auto pair : collection|aspairs) { Do-something_with (pair.first); }
在这个想法中, pair
被设置为原始集合的一对双生元素。 如果你有“abcde”,那么在第一次迭代中,你首先得到的是'a',第二次是'b'。 下一次通过first ='b'和second ='c'; 等等
您可以使用类似的filter方法来准备一个元组,为每个迭代项添加一个/ first / middle / last / iteration枚举,然后在循环内部进行切换。
要简单地忽略最后一个元素,请使用范围filter来保留“最后一个”。 我不知道这是否已经在Boost.Range或Rangev3正在进行中,但这是常规循环做技巧,使其“整洁”的一般方法。
下面是我喜欢用的一个小技巧:
对于双向可迭代对象: for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout << *it << (it == items.end()-1 ? "" : sep); };
for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout << *it << (it == items.end()-1 ? "" : sep); };
使用三元?
运算符我将迭代器的当前位置与item.end()-1
调用进行比较。 由于item.end()
返回的迭代器指向最后一个元素之后的位置,所以我们减less一次以获得我们实际的最后一个元素。
如果这个项目不是迭代器中的最后一个元素,我们返回我们的分隔符(在别处定义),或者如果它是最后一个元素,我们返回一个空string。
对于单向迭代(用std :: forward_listtesting): for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout << *it << (std::distance( it, items.end() ) == 1 ? "" : sep); };
for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout << *it << (std::distance( it, items.end() ) == 1 ? "" : sep); };
在这里,我们使用当前的迭代器位置和迭代器的结尾来调用std :: distance来replace之前的三态条件。
请注意,此版本既可以同时处理双向迭代,也可以处理单向迭代。
编辑:我意识到你不喜欢的.begin()
和.end()
types的迭代,但如果你想保持LOC倒计数,你可能会不得不避开基于范围的迭代在这种情况下。
如果你的比较逻辑比较简单,那么“Trick”只是将比较逻辑封装在一个单一的三元expression式中。