Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::transform with lambda: skip some items

I have some C++11 code like

std::vector<std::string> names;
std::map<std::string, std::string> first_to_last_name_map;
std::transform(names.begin(), names.end(), std::inserter(first_to_last_name_map, first_to_last_name_map.begin()), [](const std::string& i){
    if (i == "bad")
        return std::pair<std::string, std::string>("bad", "bad"); // Don't Want This
    else
        return std::pair<std::string, std::string>(i.substr(0,5), i.substr(5,5));
});

where I'm transforming a vector to a map using std::transform with a lambda function. My problem is that sometimes, as shown, I don't want to return anything from my lambda function, i.e. I basically want to skip that i and go to the next one (without adding anything to the map).

Is there any way to achieve what I'm thinking about? I can use boost if it helps. I want to avoid a solution where I have to do a pre-process or post-process on my vector to filter out the "bad" items; I should only need to look at each item once. Also, my actual logic is a bit more complicated than the if/else as written, so I think it would be nice to keep things encapsulated in this std::transform/lambda model if possible (though maybe what I'm trying to achieve isn't possible with this model).

EDIT: Just to emphasize, I'm looking to perform this operation (selectively processing vector elements and inserting them into a map) in the most efficient way possible, even if it means a less elegant solution or a big rewrite. I could even use a different map data type depending on what is most efficient.

like image 730
davewy Avatar asked Aug 17 '16 23:08

davewy


1 Answers

template<class Src, class Sink, class F>
void transform_if(Src&& src, Sink&& sink, F&& f){
  for(auto&& x:std::forward<Src>(src))
    if(auto&& e=f(decltype(x)(x)))
      *sink++ = *decltype(e)(e);
}

Now simply get a boost or std or std experiental optional. Have your f return an optional<blah>.

auto sink = std::inserter(first_to_last_name_map, first_to_last_name_map.begin());
using pair_type = decltype(first_to_last_name_map)::value_type;

transform_if(names, sink,
  [](const std::string& i)->std::optional<pair_type>{
    if (i == "bad")
      return {}; // Don't Want This
    else
      return std::make_pair(i.substr(0,5), i.substr(5,5));
  }
);

My personal preferred optional actually has begin end defined. And we get this algorithm:

template<class Src, class Sink, class F>
void polymap(Src&& src, Sink&& sink, F&& f){
  for(auto&& x:std::forward<Src>(src))
    for(auto&& e:f(decltype(x)(x)))
      *sink++ = decltype(e)(e);
}

which now lets the f return a range, where optional is a model of a zero or one element range.

like image 58
Yakk - Adam Nevraumont Avatar answered Oct 03 '22 18:10

Yakk - Adam Nevraumont