Morwenn
Morwenn

Reputation: 22552

Idiomatic and efficient way to add two ranges element-wise

Is there any efficient and idiomatic way to perform the following operation?

std::vector<int> a = { 1, 2, 3, 4 };
std::vector<int> b = { 5, 6, 7, 8 };

for (std::size_t i = 0 ; i < a.size() ; ++i)
{
    a[i] += b[i];
}

I am trying to avoid the brackets/index notation and only use iterators in order for the operation to work with any container with forward iterators. I thought of the following solution:

std::vector<int> a = { 1, 2, 3, 4 };
std::vector<int> b = { 5, 6, 7, 8 };

std::transform(a.begin(), a.end(),
               b.begin(),
               a.begin(),
               std::plus<int>());

However, there is the redundancy of a.begin() and I only get to use + and not +=. Is there some algorithm in the standard library that would allow me to use iterators without having any redundancy or do I have to write the full loops by hand?

Upvotes: 8

Views: 2986

Answers (6)

Morwenn
Morwenn

Reputation: 22552

I couldn't find the kind of generic function I was looking for and finally went with the following function that I named range_map ("map the given function element-wise with two given ranges"). As the comments point, it is actually no more than a binary std::for_each:

template<class InputIt1, class InputIt2, class BinaryOperation>
void range_map(InputIt1 first1, InputIt1 last1,
               InputIt2 first2, BinaryOperation binary_op)
{
    while (first1 != last1) {
        binary_op(*first1++, *first2++);
    }
}

I created the class plus_assign the following way:

template<typename T>
struct plus_assign
{
    void operator()(T &lhs, const T &rhs) const 
    {
        lhs += rhs;
    }
};

And then my code becomes:

std::vector<int> a = { 1, 2, 3, 4 };
std::vector<int> b = { 5, 6, 7, 8 };

range_map(a.begin(), a.end(),
          b.begin(),
          plus_assign<int>());

There is also the unary counterpart of the function range_map, to map a given functor to a range:

template<class InputIt, class BinaryOperation>
void range_map(InputIt first, InputIt last,
               UnaryOperation unary_op)
{
    while (first != last) {
        unary_op(*first1++);
    }
}

Upvotes: 1

Khurshid Normuradov
Khurshid Normuradov

Reputation: 1594

Use operator overload

#include <vector>

std::vector<int>& operator += (std::vector<int>& a, std::vector<int> const& b)
{
    for(size_t i = 0; i != a.size(); ++i)
        a[i] += b[i];

    return a;
}

int main(int argc, char * argv[])
{
   std::vector<int> a { 1, 3, 5, 7, 9};
   std::vector<int> b { 2, 4, 6, 8, 10};

   a += b;

   return 0;
}

Upvotes: 0

Escualo
Escualo

Reputation: 42072

If you were to use this more than once, and you were interested in a simple interface in the spirit of the Standard Library, you could create a simple template class for the specific use case (which I refer to as "Range Increment"), and write something like:

#include<vector>
#include<algorithm>
#include<iostream>

template<typename InputIt>
InputIt range_increment(InputIt dbeg, InputIt dend, InputIt sbeg) {
  while(dbeg!=dend) {
    *(dbeg++) += (*sbeg++);
  }
  return dbeg;
}

int main() {
  std::vector<int> a = { 1, 2, 3, 4 };
  std::vector<int> b = { 5, 6, 7, 8 };

  range_increment(a.begin(), a.end(), b.begin());

  for(auto x:a) {
    std::cout<<x<<std::endl;
  }
}

Which yields:

6
8
10
12

Upvotes: 4

Jerry Coffin
Jerry Coffin

Reputation: 490098

Perhaps something that was intended to become idiomatic, but never quite did:

std::valarray<int> a = { 1, 2, 3, 4 };
std::valarray<int> b = { 5, 6, 7, 8 };

Now you can do these

std::valarray<int> c = a + b; //apply '+' element-wise; store result in c

a += b;  //apply '+=' element-wise 

See the documentation of std::valarray for more details.

Upvotes: 9

Peter Alexander
Peter Alexander

Reputation: 54270

What's wrong with the redundancy of a.begin()?

If you're not happy with it, just invent your own algorithm: transform_inplace

template <class InputIterator, class OutputIterator, class BinaryOperator>
OutputIterator transform_inplace (InputIterator first,
                                  InputIterator last,
                                  OutputIterator result,
                                  BinaryOperator op)
{
  while (first != last) {
    *result = op(*result, *first);
    ++result; 
    ++first;
  }
  return result;
}

Upvotes: 4

dspeyer
dspeyer

Reputation: 3026

Not sure I'd call it "idiomatic", but:

assert(a.size()==b.size());
auto bi = b.begin();
for (auto& i : a) {
  i += *(bi++);
}

is pretty concise.

Upvotes: 2

Related Questions