Reputation: 219
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
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
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