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 ( strings
por 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.
filterProperty
establecido entrue
?Respuestas:
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_if
evalúa la expresión lambda para cada elementofoo
aquí y si devuelvetrue
copia el valor enbar
.El
std::back_inserter
nos permite insertar nuevos elementos al final debar
(usarpush_back()
) con un iterador sin tener que redimensionarlo primero al tamaño requerido.fuente
std::copy_if
no es más que un bucle forUn 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.fuente
remove_if
en 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 estocopy_if
, así que creo que agrega.remove_if
no cambia elsize()
. Necesitarás encadenarloerase
para eso .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 elranges::views
espacio de nombres en lugar destd::ranges::views
( demostración en vivo ).fuente
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));
fuente
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); });
fuente
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; });
fuente