2014-01-18 3 views
30

Возвращаясь к C++ после нескольких лет C# я задавался вопросом, что современный - читать: C++ 11 - способ фильтрации массива будет, то как мы можем достичь чего-то похожее на этот Linq запрос:Современный способ фильтрации контейнера STL?

var filteredElements = elements.Where(elm => elm.filterProperty == true); 

Чтобы отфильтровать вектор элементов (strings ради этого вопроса)?

Я искренне надеюсь, что старые алгоритмы стиля STL (или даже расширения, такие как boost::filter_iterator), требующие Явные методы, которые будут определены, теперь заменяются?

+0

ли это извлечь все элементы, которые 'filterProperty' установлено в' true'? –

+0

Lambdas - это ключ – Paranaix

+0

К сожалению, да. Некоторые общие критерии фильтра. – ATV

ответ

34

Смотрите пример из cplusplus.com для std::copy_if:

std::vector<int> foo = {25,15,5,-5,-15}; 
std::vector<int> bar; 

// copy only positive numbers: 
auto it = std::copy_if (foo.begin(), foo.end(), std::back_inserter(bar), [](int i){return i>=0;}); 

std::copy_if оценивает лямбда-выражения для каждого элемента в foo здесь, и если она возвращает true копирует значение, которое должно bar.

std::back_inserter позволяет фактически вставлять новые элементы в конце bar (с использованием push_back()) с итератором без необходимости сначала изменять размер до нужного размера.

+6

Действительно ли это самый близкий к LINQ, который предлагает C++? Это нетерпеливо (IOW не ленив) и очень многословно. – usr

+1

@usr Его синтаксический сахар IMO, простой цикл, также выполняет работу (и часто позволяет избежать копирования). – Paranaix

+1

Пример OPs не использует синтаксический сахар LINQ. Преимущества - ленивая оценка и композиционная способность. – usr

12

Я думаю, что Boost.Range заслуживает упоминания. Полученный код довольно близко к оригиналу:

#include <boost/range/adaptors.hpp> 

// ... 

using boost::adaptors::filtered; 
auto filteredElements = elements | filtered([](decltype(elements)::value_type const& elm) 
    { return elm.filterProperty == true; }); 

Единственным недостатком является необходимость явно объявить тип параметра лямбда в. Я использовал decltype (elements) :: value_type, потому что он избегает необходимости указывать точный тип, а также добавляет зерно общности. Кроме того, с 14 C++ в полиморфных лямбды, тип может быть просто определена как авто:

auto filteredElements = elements | filtered([](auto const& elm) 
    { return elm.filterProperty == true; }); 

filteredElements будет диапазон, пригодный для обхода, но это в основном вид оригинального контейнера. Если то, что вам нужно, это еще один контейнер, заполненный копиями элементов, удовлетворяющих критериям (таким образом, что это не зависит от времени жизни оригинального контейнера), она может выглядеть следующим образом:

using std::back_inserter; using boost::copy; using boost::adaptors::filtered; 
decltype(elements) filteredElements; 
copy(elements | filtered([](decltype(elements)::value_type const& elm) 
    { return elm.filterProperty == true; }), back_inserter(filteredElements)); 
+0

Без временного хранения +1 –

12

более эффективный подход, если вы не Фактически нужна новая копия списка, это remove_if, которая фактически удаляет элементы из исходного контейнера.

+5

@ATV Мне нравится 'remove_if' в частности, потому что это способ использовать фильтр при наличии мутации, которая быстрее, чем копирование всего нового списка. Если бы я делал фильтр в C++, я бы использовал это над 'copy_if', поэтому я думаю, что он добавляет. – djhaskin987

+4

Для вектора, как минимум, 'remove_if' не изменяет' size() '. [Для этого вам нужно связать его с помощью 'erase' (http://cpp.sh/42g7w). – rampion

+0

@rampion Да .. удалить/удалить. Еще одна красота, которая часто заставляет меня чувствовать, что я пробиваю дыры в ленте при работе на C++ (в отличие от современных языков) в эти дни ;-) – ATV

4

Мое предложение для C++ эквивалент C#

var filteredElements = elements.Where(elm => elm.filterProperty == true); 

Определить функцию шаблона, к которому вы передаете лямбда-предикат, чтобы сделать фильтрацию. Функция шаблона возвращает результат фильтрации. например:

template<typename T> 
vector<T> select_T(vector<T> inVec, function<bool(const T&)> predicate) 
{ 
    vector<T> result; 
    copy_if(inVec.begin(), inVec.end(), back_inserter(result), predicate); 
    return result; 
} 

использовать - давая тривиальные примеры:

std::vector<int> mVec = {1,4,7,8,9,0}; 

// filter out values > 5 
auto gtFive = select_T<int>(mVec, [](auto a) {return (a > 5); }); 

// or > target 
int target = 5; 
auto gt = select_T<int>(mVec, [target](auto a) {return (a > target); }); 
+0

разбросанные мысли: нет точки, указывающей тип элемента при вызове, поскольку компилятор может вывести Это. Вам не следует беспокоиться о распространении типа на вызываемый (где «Container :: value_type» будет полезно) и просто разрешить любое вызываемое. Здесь нет необходимости требовать здоровую функцию 'std ::. Это будет больше похоже на то, как stdlib выполняет общие алгоритмы. Более того, по этой причине это вовсе не требует никакого «вектора», а вместо этого будет шаблоном работать с любым контейнером, для которого доступен «back_inserter», который также включает в себя «deque» и «list», а не только 'vector' специально. –

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