Presto
Presto

Reputation: 219

What is the correct way to insert a sequence of custom elements inside range-v3 pipeline?

Suppose (this is just an example) I want to use ranges-v3 library in order to create a sequence such as this one:

2 3 7 20 30 70 200 300 700 2000 3000 7000 ...

Basically for every i I've obtained from iota(0) I want to insert a sequence 2*10^i, 3*10^i, 7*10^i into the pipeline for further processing:

#include <cmath>
#include <iostream>
#include <range/v3/all.hpp>

int main(){
    using namespace ranges::views;

    auto rng = iota(0) |
        /*
            insert 2*10^i, 3*10^i, 7*10^i
        */ |
        take_while([](int x){ return x < 10000;});

    for(auto i: rng) {
        std::cout << i << " ";
    }
    std::cout << "\n";
}

I'm not sure how to implement that properly. I managed to create a working example by returning temporary containers, as described in this answer:

#include <cmath>
#include <iostream>
#include <range/v3/all.hpp>

int main(){
    using namespace ranges::views;

    auto rng = iota(0) |
               transform([](int i) {
                   int mul = pow(10, i);
                   return std::array{2*mul, 3*mul, 7*mul};
               }) |
               cache1 |
               join |
               take_while([](int x){ return x < 10000;});

    for(auto i: rng) {
        std::cout << i << " ";
    }
    std::cout << "\n";
}

But I wonder if I can do it more directly. In fact, ranges::views::for_each sounds like a good fit there (because it flattens returned range automatically) but I'm not sure what to return from it:

auto rng = iota(0) |
    for_each([](int i){
        int mul = pow(10, i);
        return /* ?????????????????????????? */
    }) | 
    take_while([](int x){ return x < 10000;});

Or perhaps there is more idiomatic way to insert custom elements inside the pipeline?

Upvotes: 2

Views: 344

Answers (2)

Porsche9II
Porsche9II

Reputation: 661

Not nicer - but at least a different approach: https://wandbox.org/permlink/HotxyAYrtTuhstbU

#include <iostream>
#include <range/v3/all.hpp>
#include <cmath>
int main()
{
    auto two_three_seven = {2,3,7};
    auto vi = ranges::views::iota(0)
              | ranges::views::for_each([two_three_seven](int i) { 
                    return two_three_seven | ranges::views::all 
                        | ranges::views::transform([i](int x) {return x*pow(10,i);})
                    ;
                })
              | ranges::views::take_while([](int x){ return x < 10000;});
    std::cout << vi << '\n';
}

Upvotes: 1

Presto
Presto

Reputation: 219

One idea I had was to write my own view that stores data passed in std::initializer_list inside a private std::vector:

#include <vector>

template <typename T>
class store_view: public ranges::view_facade<store_view<T>> {
    friend ranges::range_access;
    std::vector<T> data_;
    size_t pos_;

    T const & read() const { return data_[pos_]; }
    bool equal(ranges::default_sentinel_t) const { return data_.size() == pos_; }
    void next() { ++pos_; }
public:
    store_view() = default;
    explicit store_view(std::initializer_list<T> data): data_{data}, pos_{0} {
    }
};

Using this view in the pipeline looks nice and tidy:

auto rng = iota(0) |
    for_each([](int i){
        int mul = pow(10, i);
        return store_view{2*mul, 3*mul, 7*mul};
    }) | 
    take_while([](int x){ return x < 10000;});

It works but it seems costly because it creates a new vector for every value in the stream. A less pretty but more efficient way would be to create a contained just once, then capture it by reference in the lambda (because it needs to outlive it), update it and return as view using ranges::views::all:

std::array<int, 3> values;
auto rng = iota(0) |
    for_each([&values](int i){
        int mul = pow(10, i);
        values[0] = 2*mul;
        values[1] = 3*mul;
        values[2] = 7*mul;
        return values | all;
    }) | 
    take_while([](int x){ return x < 10000;}); 

Still not sure if there's a better approach.

Upvotes: 0

Related Questions