2016-08-17 2 views
0

У меня есть C++ 11 подобный кодстанд :: преобразование с лямбда: пропустить некоторые элементы

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)); 
}); 

где я преобразуя вектор на карте с помощью зЬй :: преобразование с лямбда-функции. Моя проблема в том, что иногда, как показано, я не хочу ничего возвращать из своей лямбда-функции, т. Е. Я в основном хочу пропустить это i и перейти к следующему (без добавления чего-либо на карту).

Есть ли способ достичь того, о чем я думаю? Я могу использовать boost, если это помогает. Я хочу избежать решения, когда я должен выполнить предварительный процесс или пост-процесс на моем векторе, чтобы отфильтровать «плохие» элементы; Мне нужно будет только разглядывать каждый элемент один раз. Кроме того, моя фактическая логика немного сложнее, чем if/else, как написано, поэтому я считаю, что было бы неплохо сохранить вещи, инкапсулированные в эту std :: transform/лямбда-модель, если это возможно (хотя, возможно, то, чего я пытаюсь достичь с этой моделью невозможно).

EDIT: Чтобы подчеркнуть, я хочу выполнить эту операцию (выборочно обрабатывая векторные элементы и вставляя их в карту) наиболее эффективным способом, даже если это означает менее элегантное решение или большой переписать. Я мог бы даже использовать другой тип данных карты в зависимости от того, что является наиболее эффективным.

+2

лично, я бы написал функцию шаблона 'transform_if' - это отразило бы схему именования других алгоритмов типа' copy_if' и сохранит полученный код простым и понятным. – jaggedSpire

ответ

2
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); 
} 

Теперь просто получите дополнительный импульс, или std или std experiental. Попросите f вернуть 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)); 
    } 
); 

Мой личный предпочтительный факультативный фактически имеет начало конца. И мы получим этот алгоритм:

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); 
} 

который теперь позволяет f возвращение диапазона, где опция является моделью диапазона нулевой или один элемент.

+0

Мне тоже нравится этот ответ, но нужно ли также искать каждый элемент в векторе дважды, один раз внутри лямбды, а затем снова внутри if в transform_if (или неявно внутри итератора диапазона для f в polyap)? Я надеюсь, что каждый элемент в векторе будет один раз. – davewy

+0

@ davewy Что вы подразумеваете под «рассмотрением» или «поиском»? Предложение 'if' разделяет на необязательность необязательного (пустое или нет), а не на содержимое необязательного. Он перемещает содержимое из опционального в раковину, что также не «смотрит». И «поиск» выполняется по одному элементу за раз - здесь есть только один проход. Итак, я не уверен, что вы говорите. – Yakk

+0

Я имею в виду, что для каждого элемента выполняется две операции сравнения: первое сравнение '(i ==" bad ")' определяет, какое значение должно иметь опциональное, а затем второе 'if' смотрит, имеет ли значение option значение или нет. Таким образом, перед добавлением элемента в карту выполняются два сравнения. Я хочу выполнить только одно сравнение или операцию над каждым элементом вектора, прежде чем вставлять действительные в карту. – davewy

2

Вы можете использовать boost::adaptors::filtered, чтобы сначала фильтровать vector элементов, которые вы не хотите, прежде чем передавать их на номер transform.

using boost::adaptors::filtered; 
boost::transform(names | filtered([](std::string const& s) { return s != "bad"; }), 
       std::inserter(first_to_last_name_map, first_to_last_name_map.begin()), 
       [](std::string const& i) { return std::make_pair(i.substr(0,5), i.substr(5,5)); }); 

Live demo

+0

Это будет работать, но нужно ли это дважды смотреть на каждый элемент вектора, один раз, чтобы выполнить фильтр, а затем снова внутри лямбда? Я надеюсь сделать что-то подобное, но только один раз посетить каждый элемент в векторе. – davewy

+0

@ davewy Адаптеры Boost ленивы. Вот демо - http://coliru.stacked-crooked.com/a/60c28e85ec08bbe3. По какой-то причине лямбда вызывается один раз для самого первого элемента в «векторе» перед вызовом 'transform' (возможно, именно так работает композиция диапазона), но кроме этого,« вектор »перемещается в пределах вызовите 'transform'. – Praetorian

+0

Спасибо! В примере кода, который вы предоставляете, похоже, что элементы распечатываются два или три раза. Несмотря на то, что повышающие адаптеры являются ленивыми, они все еще требуют оценки, когда итератор посещает элемент. Поэтому каждый элемент посещается два или три раза, а не один раз. Есть идеи? – davewy

0

Вы можете просто первый/последний проход с std::remove_if. Например.

std::vector<std::string> names; 
std::map<std::string, std::string> first_to_last_name_map; 
std::transform(names.begin(), 
       std::remove_if(names.begin(), 
           names.end(), 
           [](const std::string &str){ 
            return str=="bad"; 
           }), 
       std::inserter(first_to_last_name_map, 
          first_to_last_name_map.begin()), 
       [](const std::string& i){ 
        return std::pair<std::string, std::string>(i.substr(0,5), i.substr(5,5)); 
       }); 

Обратите внимание, что remove_if просто сдвигает удаленные элементы прошлых итератора он возвращается.

+0

Это сработает, но для этого требуется 2 прохода через вектор, один для построения итератора, возвращаемого remove_if, и один для преобразования? Я надеюсь сделать что-то подобное, но только один раз посетить каждый элемент в векторе. – davewy

Смежные вопросы