2013-03-21 4 views
3

Время от времени мне нужно перебирать подмножество элементов контейнера или просто извлекать их и пренебрегать остальными. В итоге я создал boost::range::adaptors::filtered, чтобы создать эти ленивые коллекции.C++ собирает алгоритм?

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

Есть ли причина отсутствия сборного алгоритма (как в Руби сбор) в STL (у нас есть только copy_if, не одно и то же)? Или любая причина против использования?

Возможная реализация может быть:

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

но lazy_collect также может быть полезно, чтобы избежать копирования.

Все ответы ниже являются замечательными. Хотел бы я отметить всех. Я не знал о std::back_inserter. Сбор вещей теперь так же просто, как:

boost::copy(orig | filtered(predicate), std::back_inserter(collection)); 
+0

Возможно, вы ищете 'std :: copy_if()', определенный в ''? – Angew

+0

@ Кажется, что вы указали, что вы добавили, что 'copy_if' не то же самое. При использовании 'copy_if' вам нужно заранее знать, сколько места вам нужно, в результате чего' n container (count_if (старый, предикат)); станд :: copy_if (старый, п, сказуемое); '. – gnzlbg

+1

Как показывает мой asnwer, вы можете использовать 'back_inserter', чтобы обойти требования знания размера apriori. – Angew

ответ

6

Стандартные алгоритмы не работают непосредственно с контейнерами (создавая или уничтожая их, или изменяя их размеры). Они работают на диапазонах итераторов, и единственные изменения, которые делает алгоритм, - это назначение через итераторы.

Итак, предлагаемая вами операция полностью выходит за рамки стандартных алгоритмов. Может быть, «должен» быть большой дополнительный набор общих контейнерных операций в стандарте, включая этот, но «философия STL» - работать на итераторах.

Не-ленивая операция вы предлагаете может быть сделаны с помощью std::back_inserter и std::copy_if (при необходимости используя перемещение итератора) или move_if (если вы выбросили это сам). std::copy_if отсутствовал на C++ 03, но это был случайный недосмотр.

Леную версию нельзя выполнить, просто подключив стандартные компоненты - нет чистого способа связать «выход» одного алгоритма прямо с «входом» другого алгоритма без промежуточного хранения. Вот почему Boost предоставляет такое интересное разнообразие итераторов, а также диапазонов.

Я не знаю, почему они не были включены в C++ 11, но одна вещь, чтобы отметить о C++ 11, было то, что было довольно поздно. Предложения были отброшены из-за нехватки времени, поэтому причина может быть такой же простой, как «она никогда не считалась достаточно важной, чтобы предлагать, учитывая известную существующую рабочую нагрузку».

Что касается конкретной реализации, то не все контейнеры имеют push_back, поэтому вам, вероятно, не следует использовать Container в качестве имени параметра шаблона. PushBackSequence будет более подробно описывать требования.

+2

Также обратите внимание, что если контейнер имеет 'insert', а не' push_back', вы можете использовать ['inserter'] (http://en.cppreference.com/w/cpp/iterator/inserter) вместо' back_inserter'. –

5

Как об этом:

#include <algorithm> 
#include <iterator> 

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

Где container это контейнер, вы хотите, чтобы собрать из, и result является контейнером, который будет хранить коллекцию.

+0

OP пишет: «У нас только copy_if, что не то же самое», поэтому они, похоже, знают об этом. Я согласен, однако, что неясно, чего они хотят –

+0

Ницца, я не знал о заднем вставке. – gnzlbg

5

Из комментариев:

При использовании copy_if вам нужно знать, прежде чем руки, сколько места потребуется

Это не так.Вы можете использовать std::copy_if с back inserter:

#include <algorithm> // For std::copy_if 
#include <iterator> // For std::back_inserter 
#include <vector> 
#include <iostream> 

int main() 
{ 
    std::vector<int> v(10); 
    std::iota(begin(v), end(v), 0); // Fills the vector with 0..9 

    std::vector<int> out; 
    std::copy_if(begin(v), end(v), back_inserter(out), [] (int x) 
    //        ^^^^^^^^^^^^^^^^^^ 
    { 
     return x > 4; 
    }); 

    for (auto x : out) { std::cout << x << " "; } 
} 

Вот live example.

И если вы хотите функцию, которая работает непосредственно на контейнере, а не на диапазон, определяемый парой итераторов, вы могли бы написать следующий помощник:

template<typename C1, typename C2, typename P> 
void cont_copy_if(C1&& src, C2&& dst, P&& p) 
{ 
    std::copy_if(
     begin(std::forward<C1>(src)), 
     end(std::forward<C1>(src)), 
     back_inserter(std::forward<C2>(dst)), 
     std::forward<P>(p) 
     ); 
} 

Что вы бы просто использовать этот путь:

int main() 
{ 
    std::vector<int> v(10); 
    std::iota(begin(v), end(v), 0); 

    std::list<int> out; 
// ^^^^^^^^^ 
// You could use a different destination container 

    cont_copy_if(v, out, [] (int x) { return x > 4; }); 
// ^^^^^^^^^^^^ 

    for (auto x : out) { std::cout << x << " "; } 
} 

И, конечно, live example.

+0

Действительно, спасибо! Я не знал о back_inserter. Теперь я могу просто сделать boost :: copy (old | filter (pred), std :: back_inserter (new)) '. – gnzlbg

+0

Будьте осторожны при вызове std :: forward дважды в той же переменной (src). Если src привязан к r-значению, то вы эффективно перемещаете src в begin(), и поэтому опасно передавать его в конец(). – bstamour

+0

@bstamour: Почему я должен двигаться? 'begin()' просто вызывает функцию 'begin()' member в аргументе, если она существует –