Idiom(s) for “for each except the last” (or “between each consecutive pair of elements”) [duplicate]

安稳与你 提交于 2019-11-27 11:52:07

My way (without additional branch) is:

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

Do you know Duff's 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)

Hopefully I didn't ruin everyone's day. If you don't want to either then never use this.

(mumble) I'm so sorry ...


The device handling 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;
}

Excluding an end element from iteration is the sort of thing that Ranges proposal is designed to make easy. (Note that there are better ways to solve the specific task of string joining, breaking an element off from iteration just creates more special cases to worry about, such as when the collection was already empty.)

While we wait for a standardized Ranges paradigm, we can do it with the existing ranged-for with a little 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 that works with ranged-for, a Bidirectional iterator is needed, for similar skip_first it is sufficient to have a Forward iterator.

I don't know of any special idioms for this. However, I prefer to special case the first and then perform the operation on the remaining items.

#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"

Typically I do it the opposite way:

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

I like plain control 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 in a single line:

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

I don't thing you can get around having a special case somewhere... For example, Boost's String Algorithms Library has a join algorithm. If you look at its implementation, you'll see a special case for the first item (no proceeding delimitier) and a then delimiter is added before each subsequent element.

You could define a function for_each_and_join that takes two functors as argument. The first functor does work 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 << ","; }
    );
}

Live example: 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 << ", ");

Output:

1, 2, 3 

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

I don't know about "idiomatic", but C++11 provides std::prev and std::next functions 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()));
    }
}

I like the boost::join function. So for more general behavior, you want a function that is called for each pair of items and can have a persistent state. You'd use it as a funcgion call with a lambda:

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

Now you can get back to a regular range-based for loop by using range filters!

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

In this idea, pair is set to a pair of adjecent elements of the original collection. If you have "abcde" then on the first iteration you are given first='a' and second='b'; next time through first='b' and second='c'; etc.

You can use a similar filter approach to prepare a tuple that tags each iteration item with an enumeration for /first/middle/last/ iteration and then do a switch inside the loop.

To simply leave off the last element, use a range filter for all-but-last. I don't know if that's already in Boost.Range or what might be supplied with Rangev3 in progress, but that's the general approach to making the regular loop do tricks and making it "neat".

Heres a little trick I like to use:

For bi-directionally iterable objects: 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 against the item.end()-1 call. Since the iterator returned by item.end() refers to the position after the last element, we decrement it once to get our actual last element.

If this item isn't the last element in the iterable, we return our separator (defined elsewhere), or if it is the last element, we return an empty string.

For single direction iterables (tested with std::forward_list): for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout << *it << (std::distance( it, items.end() ) == 1 ? "" : sep); };

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

Note, this version works with both bidirectional iterables as well as single direction iterables.

EDIT: I realize you dislike the .begin() and .end() type iteration, but if you're looking to keep the LOC count down, you're probably going to have to eschew range based iteration in this case.

The "Trick" is simply wrapping the comparison logic within a single ternary expression, if your comparison logic is relatively simple.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!