¿Forma moderna de filtrar el contenedor STL?

98

Volviendo a C ++ después de años de C #, me preguntaba cuál sería la forma moderna (léase: C ++ 11) de filtrar una matriz, es decir, cómo podemos lograr algo similar a esta consulta de Linq:

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

¿Para filtrar un vector de elementos ( stringspor el bien de esta pregunta)?

Espero sinceramente que los viejos algoritmos de estilo STL (o incluso extensiones como boost::filter_iterator) que requieren la definición de métodos explícitos sean reemplazados por ahora.

Canal de televisión británico
fuente
¿Recupera todos los elementos que se han filterPropertyestablecido en true?
Joseph Mansfield
Lo siento, sí. Algún criterio de filtro genérico ..
ATV
3
También hay algunas bibliotecas que intentan emular los métodos LINQ de .NET: Linq ++ y cpplinq . No he trabajado con ellos, pero supongo que son compatibles con contenedores STL.
Dirk
1
Debe tener más claro lo que quiere, ya que el conjunto de personas competentes tanto en C ++ como en C # es pequeño. Describe lo que quieres que haga.
Yakk - Adam Nevraumont

Respuestas:

117

Vea el ejemplo 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_ifevalúa la expresión lambda para cada elemento fooaquí y si devuelve truecopia el valor en bar.

El std::back_inserternos permite insertar nuevos elementos al final de bar(usar push_back()) con un iterador sin tener que redimensionarlo primero al tamaño requerido.

Sebastián Hoffmann
fuente
30
¿Es esto realmente lo más cercano a LINQ que C ++ tiene para ofrecer? Esto es ansioso (OIA no es perezoso) y muy detallado.
usr
1
@usr Su azúcar sintáctico de la OMI, un bucle for simple también hace el trabajo (y a menudo permite evitar la copia).
Sebastian Hoffmann
1
El ejemplo de OP no utiliza ningún azúcar sintáctico LINQ. Los beneficios son evaluación perezosa y componibilidad.
usr
1
@usr, que todavía se puede lograr fácilmente con un bucle for simple, std::copy_ifno es más que un bucle for
Sebastian Hoffmann
13
@Paranaix Se puede decir que todo es simplemente azúcar sintáctico sobre el ensamblaje. El punto es, no escribir bucles for, cuando un algoritmo se puede componer claramente de una manera legible usando operaciones primitivas (como filtro). Muchos lenguajes ofrecen esta característica; en C ++, desafortunadamente, todavía es complicado.
BartoszKP
47

Un enfoque más eficiente, si en realidad no necesita una nueva copia de la lista, es remove_if, que en realidad elimina los elementos del contenedor original.

djhaskin987
fuente
7
@ATV Me gusta remove_ifen particular porque es la forma de usar el filtro en presencia de una mutación, que es más rápido que copiar una lista completamente nueva. Si estuviera haciendo un filtro en C ++, usaría esto copy_if, así que creo que agrega.
djhaskin987
16
Para vector, al menos, remove_ifno cambia el size(). Necesitarás encadenarlo erasepara eso .
rampion
5
@rampion Sí ... borrar / eliminar. Otra belleza que con frecuencia me hace sentir como si estuviera haciendo agujeros en una cinta cuando trabajo en C ++ (a diferencia de los lenguajes modernos) en estos días ;-)
ATV
1
El borrado explícito es una característica. No es necesario borrar en todos los casos. A veces, los iteradores son suficientes para continuar. En tales casos, un borrado implícito produciría una sobrecarga innecesaria. Además, no todos los contenedores son redimensionables. std :: array, por ejemplo, no tiene ningún método de borrado.
Martin Fehrs
31

En C ++ 20, use la vista de filtro de la biblioteca de rangos: (requiere #include <ranges>)

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

devuelve perezosamente los elementos pares en vec.

(Consulte [range.adaptor.object] / 4 y [range.filter] )


Esto ya es compatible con GCC 10 ( demostración en vivo ). Para Clang y versiones anteriores de GCC, la biblioteca range-v3 original también se puede usar, con #include <range/v3/view/filter.hpp>(o #include <range/v3/all.hpp>) y el ranges::viewsespacio de nombres en lugar de std::ranges::views( demostración en vivo ).

LF
fuente
Debe proporcionar el espacio de nombres #include y usar necesario para que su respuesta se compile. Además, ¿qué compilador admite esto a día de hoy?
gsimard
2
@gsimard ¿Mejor ahora?
LF
1
Si alguien intenta hacer esto en macOS: a partir de mayo de 2020, libc ++ no es compatible con esto.
Dax
25

Creo que Boost.Range también merece una mención. El código resultante es bastante parecido al original:

#include <boost/range/adaptors.hpp>

// ...

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

El único inconveniente es tener que declarar explícitamente el tipo de parámetro de lambda. Usé decltype (elements) :: value_type porque evita tener que deletrear el tipo exacto y también agrega un grano de genérico. Alternativamente, con las lambdas polimórficas de C ++ 14, el tipo podría especificarse simplemente como auto:

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

FilterElements sería un rango, adecuado para el recorrido, pero básicamente es una vista del contenedor original. Si lo que necesita es otro contenedor lleno de copias de los elementos que satisfacen los criterios (de modo que sea independiente de la vida útil del contenedor original), podría verse así:

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));
usuario2478832
fuente
12

Mi sugerencia para C ++ equivalente a C #

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

Defina una función de plantilla a la que pase un predicado lambda para realizar el filtrado. La función de plantilla devuelve el resultado filtrado. p.ej:

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

para usar - dando ejemplos triviales:

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
fuente
11

Se mejoró el código pjm siguiendo las sugerencias de subrayado-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.
fuente