Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ algorithm for applying a function to consecutive elements

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 }
like image 281
Felix Dombek Avatar asked May 19 '16 10:05

Felix Dombek


4 Answers

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;
    }
}
like image 55
eerorika Avatar answered Nov 06 '22 14:11

eerorika


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

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

like image 42
sehe Avatar answered Nov 06 '22 12:11

sehe


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

like image 1
Nick Matteo Avatar answered Nov 06 '22 13:11

Nick Matteo


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);
    }
}
like image 1
Marshall Clow Avatar answered Nov 06 '22 12:11

Marshall Clow