16

Is there a simpler way to write this, e.g. by using an STL or boost algorithm?

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container
std::vector<int> result;
std::transform(v.begin(), v.end() - 1, // (0, 1, 2)
               v.begin() + 1,          // (1, 2, 3)
               std::back_inserter(result),
               [](int a, int b){ return a + b; }); // any binary function
// result == { 1, 3, 5 }
Felix Dombek
  • 13,664
  • 17
  • 79
  • 131
  • 1
    @erip Iterators and reverse iterators are not compatible types. `v.end()-1` and `v.rbegin()` point to the same element, but can't be compared. – interjay May 19 '16 at 11:00
  • @interjay Whoops. :) – erip May 19 '16 at 11:03
  • 1
    Seems an unanswered discussion has already been brought up here: http://stackoverflow.com/questions/19927563/for-each-that-gives-two-or-n-adjacent-elements Also, I'm sure I've seen a blog post with an implementation, I'll post it as an answer if I can find it... – Brian Rodriguez May 19 '16 at 11:06

5 Answers5

10

I propose using a for loop:

for(std::vector::size_type i = 0; i < v.size() - 1; i++)
    result.push_back(v[i] + v[i+1])

A more generic loop for bidirectional iterators:

// let begin and end be iterators to corresponding position
// let out be an output iterator
// let fun be a binary function
for (auto it = begin, end_it = std::prev(end); it != end_it; ++it)
   *out++ = fun(*it, *std::next(it));

We can go a bit further and write a loop for forward iterators:

if(begin != end) {
    for (auto curr = begin,
         nxt = std::next(begin); nxt != end; ++curr, ++nxt) {
        *out++ = fun(*curr, *nxt);
    }
}

Finally, and algorithm for input iterators. However, this one requires that the value type is copyable.

if(begin != end) {
    auto left = *begin;
    for (auto it = std::next(begin); it != end; ++it) {
        auto right = *it;
        *out++ = fun(left, right);
        left = right;
    }
}
eerorika
  • 232,697
  • 12
  • 197
  • 326
9

The binary version of std::transform can be used.

The std::adjacent_find/std::adjacent_difference algorithms can be abused.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    The difference to my algorithm is that `adjacent_difference` copies the first element to the output sequence unmodified. This is actually quite stupid because it introduces the need to have compatible input and output types. :/ – Felix Dombek May 19 '16 at 11:36
1

I would write your own algorithm to apply a functor to each pair of elements in the container.

(Shameless blurb) In my ACCU presentation this year, "STL Algorithms – How to Use Them and How to Write Your Own", showed how to write one like this. I called it adjacent_pair (about 25:00 into the video)

template <typename ForwardIterator, typename Func>
void adjacent_pair(ForwardIterator first, ForwardIterator last, Func f)
{
    if (first != last)
    {
        ForwardIterator trailer = first;
        ++first;
        for (; first != last; ++first, ++trailer)
            f(*trailer, *first);
    }
}
Marshall Clow
  • 15,972
  • 2
  • 29
  • 45
1

std::adjacent_difference is for exactly this, but as you mentioned, it copies the first element to the result, which you don't want. Using Boost.Iterator, it's pretty easy to make a back_inserter which throws away the first element.

#include <boost/function_output_iterator.hpp>

template <class Container>
auto mybackinsrtr(Container& cont) {
    // Throw away the first element
    return boost::make_function_output_iterator(
            [&cont](auto i) -> void { 
              static bool first = true;
              if (first)
                first = false;
              else
                cont.push_back(i);
            });
}

Then you can #include <boost/range/numeric.hpp> and do this:

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container
std::vector<int> result;
boost::adjacent_difference(v, mybackinsrtr(result), std::plus<>{}); // any binary function

See it on ideone


When you want your binary function to return a different type (such as a string), the above solution won't work because, even though the insertion cont.push_back(i) is never called for the first copied element, it still must be compiled and it won't go.

So, you can instead make a back_inserter that ignores any elements of a different type than go in the container. This will ignore the first, copied, element, and accept the rest.

template <class Container>
struct ignore_insert {
    // Ignore any insertions that don't match container's type
    Container& cont;
    ignore_insert(Container& c) : cont(c) {}
    void operator() (typename Container::value_type i) {
        cont.push_back(i);
    }
    template <typename T>
    void operator() (T) {}
};

template <class Container>
auto ignoreinsrtr(Container& cont) {
    return boost::make_function_output_iterator(ignore_insert<Container>{cont});
}

Then you can use it similarly.

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container
std::vector<std::string> result;
boost::adjacent_difference(v, ignoreinsrtr(result), [](int a, int b){ return std::to_string(a+b); });

On ideone

Nick Matteo
  • 4,453
  • 1
  • 24
  • 35
  • Nice, but still doesn't work for a functor which would change the type of the iterator (`[](int a, int b){std::to_string(a+b);}`) however this functionality could just be put into the output iterator, eliminating the need for any functor here and you could just use `std::copy`. – Felix Dombek May 19 '16 at 16:26
  • @FelixDombek: I'm not sure why that's not working. You can deal with that particular case [like I did here](http://ideone.com/OOaDKP), replacing the lambda back inserter with a struct that ignores `int`s and inserts `string`s. – Nick Matteo May 19 '16 at 16:48
  • It doesn't work with `adjacent_difference` because it copies the first element to the result range instead of applying the functor. So – because the result iterator needs to be able to accept an element of the source type – the functor cannot have any incompatible return type either. – Felix Dombek May 19 '16 at 16:56
  • @FelixDombek: It doesn't check though, at least in libstdc++. It just assigns the result of the argment `binary_op` to the output iterator: `*++__result = __binary_op(__tmp, __value);` That's why it works with the example I linked in my comment. – Nick Matteo May 19 '16 at 16:59
0

Stephan T. Lavavej has written a nice adjacent_iterator class here:

How do I loop over consecutive pairs in an STL container using range-based loop syntax?

This could also be used here.

Community
  • 1
  • 1
Felix Dombek
  • 13,664
  • 17
  • 79
  • 131