I have some issues with the C++ iterators. Coming from some other languages, I am used to syntax like:
1 2
let numbers = vec![2, 3, 4, 5, 6];
let sum_doubles: i32 = numbers.map(|n| 2 * n).sum();
I'm kind of struggling to get something like this working in C++. With a lot of pain I was able to implement an iterator, iter_map (see https://github.com/xfbs/euler/blob/master/lib/cpp/include/euler/iterators.hpp) that takes an existing iterator, and applies a function to each element, such that the code would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// input numbers
std::vector<int> numbers = {2, 3, 4, 5, 6};
auto numbers = std::begin(numbers);
auto numbers_end = std::end(numbers);
// double each number iterator
auto double_num = [](const uint64_t &num) {
return 2 * num;
};
auto numbers_doubled = map_iter(numbers, double_num);
auto numbers_doubled_end = map_iter(numbers_end, double_num);
// compute sum
auto sum_doubles = std::accumulate(numbers_doubled, numbers_doubled_end, 0);
However, as you can easily see, even this is WAY more verbose than the rust/ruby/crystal/scheme or in general the functional approach to code like this.
Is there a way, with the STL, to rewrite this code in a cleaner, more concise way? Or is there a way to write a custom iterator class that has easy and reusable methods like .map(), .fold(), .sum(), etc?
C++ has it's own way of doing things, so trying to do it in the same way like in other languages is probably not the best way.
1 2 3 4 5 6 7 8
std::vector<int> numbers = { 2, 3, 4, 5, 6 };
// double numbers in vector
for (int& num: numbers)
num *= 2;
// get the sum
double sum = std::accumulate(numbers.begin(), numbers.end(), 0.0);
You can avoid a raw for loop with std::transform. std::transform(numbers.begin(), numbers.end(), numbers.begin(), [](int i) -> int { return i * 2; });
Now which is more legible is a different subjective matter...
It turns out it's possible to supply lazy generic operations over a range of any type. If you're interested in how this works, read the below, or consider asking another question.
@Thomas1965: I totally get your point.. I mean I have to work with the language, not fight it! :p
I think that all of your solutions are pretty good and more concise than mine (it's obvious that I haven't done c++ for 6 years...), and I guess I should have asked my question a little different but, the problem with these answers is that they don't scale well, imagine if I'm working with a set of 2 million integers, and I need to filter that or run functions on it (mapping the data) multiple times, I'll have a lot of overhead from copying stuff around.
I'm definitely going to scrap my attempts at implementing this since that wasn't a smart idea to start with (more like an experiment). I'm also going to try to stay away from boost for now, because it's one huge library to pull in for such a small feature. But I did have a look at range-v3 and it seems like a pretty good match for what I need!
..... the problem with these answers is that they don't scale well, imagine if I'm working with a set of 2 million integers, and I need to filter that or run functions on it (mapping the data) multiple times, I'll have a lot of overhead from copying stuff around.
You might be surprised at how efficient std::vector is. C++ has move semantics, references, copy elision, there are cache effects too.
IMO the answers presented so far do scale very well.
Regards
Edit: you might get more mileage if you present your actual problem, rather than an abstract version of it :+)
One big advantage of these iterator adaptors is that it reduces the number of "steps" required for a computation.
For example, A solution like OP's call to std::accumulate with map_iterator, or transform_reduce as above combines the steps of doubling each element and summing them so that only one pass over the elements is required. This extends nicely to other kinds of sequences (even infinite sequences) and for any number of steps.