Maneira moderna de filtrar o contêiner STL?

102

Voltando ao C ++ depois de anos de C #, eu estava me perguntando qual seria a forma moderna - leia-se: C ++ 11 - de filtrar um array, ou seja, como podemos conseguir algo semelhante a esta consulta Linq:

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

Para filtrar um vetor de elementos (strings por causa desta questão)?

Eu sinceramente espero que os algoritmos de estilo STL antigos (ou mesmo extensões como boost::filter_iterator) que exigem a definição de métodos explícitos sejam substituídos agora.

ATV
fonte
Isso recupera todos os elementos filterPropertydefinidos como true?
Joseph Mansfield
Desculpe, sim. Algum critério de filtro genérico ..
ATV
3
Existem também algumas bibliotecas que tentam emular os métodos LINQ do .NET: Linq ++ e cpplinq . Eu não trabalhei com eles, mas meu palpite é que eles suportam contêineres STL.
Dirk de
1
Você deve ser mais claro sobre o que deseja, pois o conjunto de pessoas competentes em C ++ e C # é pequeno. Descreva o que você deseja fazer.
Yakk - Adam Nevraumont

Respostas:

124

Veja o exemplo de cplusplus.com para std::copy_if:

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

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

std::copy_ifavalia a expressão lambda para cada elemento fooaqui e, se retornar true, copia o valor para bar.

O std::back_inserterpermite inserir novos elementos no final de bar(usando push_back()) com um iterador sem ter que redimensioná-lo para o tamanho necessário primeiro.

Sebastian Hoffmann
fonte
30
Isso é realmente o mais próximo do LINQ que o C ++ tem a oferecer? Isso é ansioso (IOW, não preguiçoso) e muito prolixo.
usr
1
@usr Seu açúcar sintático IMO, um loop for simples também faz o trabalho (e frequentemente permite evitar a cópia).
Sebastian Hoffmann
1
O exemplo de OPs não usa nenhum açúcar sintático LINQ. Os benefícios são avaliação preguiçosa e composição.
usr
1
@usr Que ainda pode ser alcançado por um simples for-loop facilmente, std::copy_ifnão é mais do que um for-loop
Sebastian Hoffmann
15
@Paranaix Tudo pode ser considerado apenas açúcar sintático sobre montagem. A questão é, não escrever loops for, quando um algoritmo pode ser claramente composto de uma forma legível usando operações primitivas (como filtro). Muitas linguagens oferecem esse recurso - em C ++, infelizmente, ainda é confuso.
BartoszKP de
48

Uma abordagem mais eficiente, se você realmente não precisa de uma nova cópia da lista, é remove_ifremover os elementos do contêiner original.

djhaskin987
fonte
7
Gosto remove_ifespecialmente de @ATV porque é a maneira de usar o filtro na presença de mutação, que é mais rápido do que copiar uma lista inteira. Se eu estivesse fazendo filtro em C ++, eu usaria isso copy_if, então acho que adiciona.
djhaskin987
16
Para vetor, pelo menos, remove_ifnão altera o size(). Você precisará acorrentá-lo erasepara isso .
aumento de
5
@rampion Sim .. apagar / remover. Outra beleza que frequentemente me faz sentir como se estivesse fazendo furos em uma fita ao trabalhar em C ++ (ao contrário das linguagens modernas) atualmente ;-)
ATV
1
O apagamento explícito é um recurso. Você não precisa apagar em todos os casos. Às vezes, os iteradores são suficientes para continuar. Em tais casos, um apagamento implícito produziria sobrecarga desnecessária. Além disso, nem todo contêiner é redimensionável. std :: array, por exemplo, não tem nenhum método de apagamento.
Martin Fehrs
35

Em C ++ 20, use a visualização de filtro da biblioteca de intervalos: (requer #include <ranges>)

// namespace views = std::ranges::views;
vec | views::filter([](int a){ return a % 2 == 0; })

devolve preguiçosamente os elementos pares em vec.

(Veja [range.adaptor.object] / 4 e [range.filter] )


Isso já é suportado pelo GCC 10 ( demonstração ao vivo ). Para Clang e versões anteriores do GCC, a biblioteca range-v3 original também pode ser usada, com #include <range/v3/view/filter.hpp>(ou #include <range/v3/all.hpp>) e o ranges::viewsnamespace em vez de std::ranges::views( demonstração ao vivo ).

LF
fonte
Você deve fornecer o #include e usar o namespace necessário para compilar sua resposta. Além disso, qual compilador oferece suporte para isso hoje?
gsimard
2
@gsimard está melhor agora?
LF
2
Se alguém tentar fazer isso no macOS: Em maio de 2020, libc ++ não oferece suporte para isso.
Dax
25

Acho que Boost.Range também merece uma menção. O código resultante é muito parecido com o original:

#include <boost/range/adaptors.hpp>

// ...

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

A única desvantagem é ter que declarar explicitamente o tipo de parâmetro do lambda. Usei decltype (elements) :: value_type porque evita ter que soletrar o tipo exato e também adiciona um grão de genericidade. Como alternativa, com os lambdas polimórficos do C ++ 14, o tipo poderia ser simplesmente especificado como automático:

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

FilterElements seria um intervalo adequado para travessia, mas é basicamente uma visualização do contêiner original. Se você precisar de outro contêiner preenchido com cópias dos elementos que satisfaçam os critérios (de modo que seja independente do tempo de vida do contêiner original), poderia ser assim:

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));
user2478832
fonte
12

Minha sugestão para C ++ equivalente a C #

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

Defina uma função de modelo para a qual você passa um predicado lambda para fazer a filtragem. A função de modelo retorna o resultado filtrado. por exemplo:

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

usar - dando exemplos triviais:

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); });
pjm
fonte
11

Código pjm aprimorado seguindo sugestões de sublinhado-d :

template <typename Cont, typename Pred>
Cont filter(const Cont &container, Pred predicate) {
    Cont result;
    std::copy_if(container.begin(), container.end(), std::back_inserter(result), predicate);
    return result;
}

Uso:

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

auto filteredVec = filter(myVec, [](int a) { return a > 5; });
Alex P.
fonte