Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can we use std::transform, if we don't want to transform each element into one transformed element, but two?

How can we use std::transform, if we don't want to transform each element into one transformed element, but two?

The following pseudo code illustrates what I want to achieve

std::transform(a.cbegin(), a.cend(), std::back_inserter(b), [](T const& x) {
    return f(x) and g(x);
});

Of course, I could invoke std::transform two times, but that would be annoying. Maybe we need to provide a custom inserter. Any other option?

like image 491
0xbadf00d Avatar asked Apr 25 '15 14:04

0xbadf00d


2 Answers

transform is only for doing a one-to-one transformation. A custom inserter wouldn't help you anyway since transform is implemented something like this:

while (first1 != last1) {
    *d_first++ = unary_op(*first1++); // you have no way to write
                                      // more than one element
}
return d_first;

You would actually have to write a custom iterator for a to iterate over each element twice, and then keep state in your functor to know if you're on the f state or the g state. You can see how complicated this is getting.

Anything outside of simple 1-1 transformations, you should you just use a for loop:

for (const auto& x : a) {
    b.push_back(f(x));
    b.push_back(g(x));
}

And even for simple 1-1 transformations, I think a simple range-for expression wins too.

You could additionally write your own transform that takes an arbitrary number of functors:

template <typename InIt, typename OutIt, typename... Functors>
void transform(InIt first, InIt last, OutIt d_first, Functors... fs)
{
    while (first != last) {
        apply(*first, d_first, fs...);
        first++;
    }
}

with;

template <typename In, typename OutIt>
void apply(const In&, OutIt ) { }

template <typename In, typename OutIt, typename F, typename... Functors>
void apply(const In& in, OutIt& out, F f, Functors... fs)
{
    *out++ = f(in);
    apply(in, out, fs...);
}

used as (example):

transform(a.begin(), a.end(), back_inserter(b), f, g);
like image 111
Barry Avatar answered Oct 11 '22 16:10

Barry


Usually in such cases you can use standard algorithm std::accumulate declared in header <numeric>.

For example

#include <iostream>
#include <vector>
#include <iterator>
#include <numeric>
#include <type_traits>

int main()
{
    int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    std::vector<int> v;
    v.reserve( 2 * std::extent<decltype( a )>::value );

    std::accumulate( std::begin( a ), std::end( a ),
                     std::back_inserter( v ),
                     []( auto it, auto x )
                     {
                        return *it++ = x * x, *it++ = x * x * x, it;  
                     } );

    auto it = v.begin();                     
    for ( int x : a )
    {
        std::cout << x << '\t' << *it++ << '\t';
        std::cout << *it++ << std::endl;
    }

    return 0;
}

The program output is

1   1   1
2   4   8
3   9   27
4   16  64
5   25  125
6   36  216
7   49  343
8   64  512
9   81  729
10  100 1000
like image 34
Vlad from Moscow Avatar answered Oct 11 '22 17:10

Vlad from Moscow