Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ collect algorithm?

Every now and then I need to iterate over a subset of the elements of a container or just want to extract them and neglect the rest. I end up using boost::range::adaptors::filtered to create this lazy collections.

for(auto&& i : container | filtered(predicate)) {
  // .. do stuff
}

Is there a reason for the lack of a collect algorithm (as in Ruby's collect) in the STL (we only have copy_if which is not the same)? Or any reason against using it?

A possible implementation could be:

template<class Container, class Predicate>
Container collect(Container&& c, Predicate&& p) {
  Container n;
  for(auto&& i : c) {
    if(p(i)) {
      n.push_back(i);
    }
  }
  return n;
}

but a lazy_collect might also be useful to avoid copying.

All answers below are great. I wish I could mark all of them. I didn't knew about std::back_inserter. Collecting stuff is now as easy as:

boost::copy( orig | filtered(predicate), std::back_inserter(collection));
like image 326
gnzlbg Avatar asked Dec 04 '22 11:12

gnzlbg


2 Answers

Standard algorithms don't operate directly on containers (creating or destroying them, or changing their sizes). They operate on iterator ranges, and the only changes the algorithm make are via assignment through iterators.

So, your proposed operation is entirely outside the scope of the standard algorithms. Maybe there "should" be a large extra set of generic container-operations in the standard, including this one, but the "STL philosophy" is to operate on iterators.

The non-lazy operation you propose can be done using std::back_inserter and std::copy_if (optionally using move iterators) or move_if (if you roll that yourself). std::copy_if was missing from C++03, but that was an accidental oversight.

The lazy version can't be done just by plugging together standard components -- there's no clean way to chain the "output" of one algorithm straight into the "input" of another algorithm without intermediate storage. That's why Boost provides such an interesting variety of iterators, as well as Ranges.

I don't know why they weren't incorporated into C++11, but one thing to note about C++11 was that it was rather late. Proposals were dropped for lack of time, so the reason might be as simple as "it was never considered important enough to propose, given the known existing workload".

Regarding your particular implementation, not all containers have push_back, and so you probably shouldn't use Container as the template parameter name. PushBackSequence would be more descriptive of the requirements.

like image 129
Steve Jessop Avatar answered Jan 01 '23 19:01

Steve Jessop


How about this:

#include <algorithm>
#include <iterator>

std::copy_if(container.begin(), container.end(), std::back_inserter(result), []{...})

Where container is the container you want to collect from, and result is the container which will store the collection.

like image 27
Angew is no longer proud of SO Avatar answered Jan 01 '23 21:01

Angew is no longer proud of SO