Idiom for "for each but the last" (or "between each consecutive pair of elements")

Everyone faces this problem at some point:

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

... and you get an extra delimiter that you don't want at the end. Sometimes it does not print, but, say, performs some other action, but in order for sequential actions of the same type to require some kind of separator action, but the latter does not.

Now, if you are working with the old school for loops and arrays, you would do

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

(or you can exclude the last element from the loop.) If you have something that allows non-destructive iterators, even if you don't know its size, you can do:

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

I do not like the aesthetics of the last two, and usually for loops. Can I get the same effect as with the last two, but using stronger C ++ 11ish constructs?




To expand the question further (outside of, say, this ), I will say that I would also like the first or last element to not have a special case explicitly. This is an “implementation detail” that I don’t want to worry about. So, in imaginary-future-C ++ something like this is possible:
 for(const auto& item : items) { cout << item; } and_between { cout << separator; } 
+72
c ++ idioms c ++ 11 separator
Feb 12 '16 at 21:51
source share
12 answers

My path (no extra branch):

 const auto separator = "WhatYouWantHere"; const auto* sep = ""; for(const auto& item : items) { std::cout << sep << item; sep = separator; } 
+68
Feb 12 '16 at 22:07
source share

Exclusion of the final element from the iteration is what the Ranges proposal offers for simplification. (Note that there are more efficient ways to solve the specific task of combining strings; disconnecting an element from iteration just creates more special cases that you need to worry about, for example, when the collection was already empty.)

While we are expecting a standardized Ranges paradigm, we can do this with the existing range with a small helper class.

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

and now you can write

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

Demo: http://rextester.com/MFH77611

For skip_last , which works with ranged-for, a bidirectional iterator is required; for a similar skip_first enough to have a Forward iterator.

+23
Feb 12 '16 at 22:03
source share

Do you know a Duff device ?

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

(Live)

Hope I haven't messed up all day. If you do not want or never use it.

(mumble) I'm really sorry ...




A device that processes empty containers (ranges):

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

Live test :

 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; } 
+23
Feb 13 '16 at 0:31
source share

I do not know any special idioms for this. However, I prefer the special case first, and then perform the operation on the remaining elements.

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

Output: "1, 2, 3, 4, 5"

+13
Feb 12 '16 at 21:54
source share

Usually I do it the other way around:

 bool first=true; for(const auto& item : items) { if(!first) cout<<separator; first = false; cout << item; } 
+12
Feb 12 '16 at 21:57
source share

I like simple governance structures.

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

Depending on your taste, you can put the increment and test on the same line:

 ... while (true) { std::cout << *first; if (++first == last) break; std::cout << separator; } 
+7
Feb 13 '16 at 3:43
source share

I do not know what you can find somewhere in a special case ... For example, the Boost String Algorithm Library has a join . If you look at its implementation , you will see a special case for the first element (without a subsequent separator), and then a separator will be added before each subsequent element.

+5
Feb 12 '16 at 22:39
source share

You can define a for_each_and_join function that takes two functors as an argument. The first functor works with each element, the second works with each pair of adjacent elements:

 #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 << ","; } ); } 

Real-time example: http://ideone.com/fR5S9H

+5
Feb 13 '16 at 8:44
source share
 int a[3] = {1,2,3}; int size = 3; int i = 0; do { std::cout << a[i]; } while (++i < size && std::cout << ", "); 

Output:

 1, 2, 3 

The goal is to use the && mode. If the first condition is true, it evaluates the second. If this is not true, then the second condition is skipped.

+5
Feb 13 '16 at 15:14
source share

I do not know about the "idiomatic", but C ++ 11 provides the functions std::prev and std::next for bidirectional iterators.

 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())); } } 
+3
Feb 12 '16 at 22:11
source share

I like the boost::join function. Therefore, for a more general behavior, you need a function that is called for each pair of elements and can have a constant state. You would use this as calling funcgion with a lambda:

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

Now you can return to the range-based regular for loop using range filters !

 for (auto pair : collection|aspairs) { Do-something_with (pair.first); } 

In this idea, pair defines a pair of adjacent elements in the source set. If you have "abcde", then in the first iteration you are assigned the first = "a", and the second = "b"; next time through first = 'b' and second = 'c'; and etc.

You can use a similar filter approach to prepare a tuple, which marks each iteration element with an enumeration for / first / middle / last / iteration, and then performs a switch inside the loop.

To just leave the last item, use the all-but-last range filter. I don’t know if this is already in Boost.Range or what can be delivered with Rangev3 in the process, but that the general approach to creating a regular loop does the tricks and makes it “neat”.

+3
Feb 13 '16 at 19:58
source share

Here is a little trick I like:

For bidirectional iteration objects: 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); };

Using the ternary operator ? , I compare the current position of the iterator with a call to item.end()-1 . Since the iterator returned by item.end() refers to the position after the last item, we decrease it once to get our actual last item.

If this element is not the last element in the iterable, we return our delimiter (defined elsewhere), or if it is the last element, we return an empty string.

For iterations of the same name (checked with std :: forward_list): 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); };

Here we replace the previous ternary condition with a call to std :: distance, using the current location of the iterator and the end of the iterable.

Note. This version works with both bidirectional iterators and one-way iterators.

EDIT: I understand that you don't like iterations like .begin() and .end() , but if you want the LOC counter to decrease, then you probably have to avoid range-based iteration.

"Trick" simply wraps the comparison logic in a single triple expression if your comparison logic is relatively simple.

+2
Feb 13 '16 at 19:30
source share



All Articles