2009-10-14 17 views
12

Después de haber sido contaminado por Linq, soy reacio a renunciar a él. Sin embargo, para algunas cosas solo necesito usar C++.C++ operaciones de iterador LINQ-like

La verdadera fortaleza de linq como consumidor de linq (es decir, para mí) no radica en los árboles de expresión (que son complejos de manipular), sino en la facilidad con la que puedo mezclar y combinar varias funciones. ¿Existen los equivalentes de .Where, .Select y .SelectMany, .Skip y .Take y .Concat para los iteradores de estilo C++?

Estos serían extremadamente útiles para todo tipo de código común que escribo.

No me preocupan los aspectos específicos de LINQ, la cuestión clave aquí es poder expresar algoritmos en un nivel superior, no para que el código C++ se vea como C# 3.0. Me gustaría poder expresar que "el resultado está formado por los primeros n elementos de concatenación de cada secuencia" y luego reutilizar dicha expresión siempre que se requiera una nueva secuencia, sin necesidad de instanciar (y con voracidad) intermedios.

+0

Posible duplicado: http://stackoverflow.com/questions/232222/is-there-a-linq-library-for-c --¡entonces vez más que pueden diferir he dudado de si esto es realmente un " exacto 'duplicado –

+2

No estoy interesado específicamente en LINQ, sino en el procesamiento de listas de estilo funcional en C++. –

+0

Específicamente, los lambda son agradables, pero no cruciales. –

Respuesta

4

No tengo experiencia concreta con LINQ, pero la biblioteca Boost.Iterator parece acercarse a lo que usted se refiere.

La idea es tener funciones (IIUC, en LINQ, toman la forma de métodos de extensión, pero eso no es fundamental), tomando un iterador y una función, combinándolos para crear un nuevo iterador.

LINQ "Donde" mapas a make_filter_iterator:

std::vector<int> vec = ...; 
// An iterator skipping values less than "2": 
boost::make_filter_iterator(_1 > 2, vec.begin()) 

LINQ "Select" mapas a make_transform_iterator:

using namespace boost::lambda; 
//An iterator over strings of length corresponding to the value 
//of each element in "vec" 
//For example, 2 yields "**", 3 "***" and so on. 
boost::make_transform_iterator(construct<std::string>('*', _1), vec.begin()) 

y pueden estar compuestos:

//An iterator over strings of length corresponding to the value of each element 
// in "vec", excluding those less than 2 
std::vector<int> vec = ...; 
boost::make_transform_iterator(construct<std::string>('*', _1), 
    boost::make_filter_iterator(_1 > 2, vec.begin()) 
) 

Sin embargo, hay son algunas cosas molestas con esto:

  • El tipo devuelto por make_xxx_iterator(some_functor, some_other_iterator) es xxx_iterator<type_of_some_functor, type_of_some_iterator>
  • El tipo de un funtor creado usando boost :: bind, lambda, o Fénix se convierte rápidamente en inmanejable grande e incómodo para escribir.

Es por eso que evité en el código anterior asignar el resultado de make_xxx_iterator a una variable. La característica "auto" de C++ 0x será muy bienvenida allí.

Pero aún así, un iterador de C++ no puede vivir "solo": tienen que venir en pares para ser útiles.Por lo tanto, incluso con "auto", sigue siendo un bocado:

auto begin = make_transform_iterator(construct<std::string>('*', _1), 
    make_filter_iterator(_1 > 2, vec.begin()) 
); 
auto end = make_transform_iterator(construct<std::string>('*', _1), 
    make_filter_iterator(_1 > 2, vec.end()) 
); 

Evitar el uso de lambda hace cosas prolijo, pero manejable:

struct MakeStringOf{ 
    MakeStringOf(char C) : m_C(C){} 
    char m_C; 

    std::string operator()(int i){return std::string(m_C, i);} 
}; 

struct IsGreaterThan{ 
    IsGreaterThan(int I) : m_I(I){} 
    int m_I; 

    bool operator()(int i){return i > m_I;} 
}; 

typedef boost::filter_iterator< 
    IsGreaterThan, 
    std::vector<int>::iterator 
> filtered; 

typedef boost::transform_iterator< 
    MakeStringOf, 
    filtered 
> filtered_and_transformed; 

filtered_and_transformed begin(
    MakeStringOf('*'), 
    filtered(IsGreaterThan(2), vec.begin()) 
); 

filtered_and_transformed end(
    MakeStringOf('*'), 
    filtered(IsGreaterThan(2), vec.end()) 
); 

El (aún no) es la biblioteca Boost.RangeEx prometedor a este respecto, ya que permite combinar los dos iteradores en un solo rango. Algo así como:

auto filtered_and_transformed = make_transform_range(
    make_filter_range(vec, _1 > 2), 
    construct<std::string>('*', _1) 
); 
4

Consulte this Google Groups thread.

vector<int> numbers = {1, 2, 3, 4, 8, 5, 9 , 24, 19, 15, 12 } 
auto query = 
    from(numbers). 
     where([](int i) { return i < 15 && i > 10}). 
     select(fields::full_object); 

No he encontrado nada más o menos "oficial" o ampliamente aceptado, pero puedes intentar contactar al autor de la publicación original.

+0

Eso es usar un compilador C++ 0x con soporte automático y lambda, puede volverse un poco más engorroso sin lambdas ('[] (int i) {...}') –

+2

¡Gracias, eso está bien! Sin embargo, el hilo enlazado no se puede usar fácilmente, y en cualquier caso, se ve demasiado enfocado en LINQ. No me importa mucho la sintaxis, solo quiero map/reduce/filter/concat, etc. el viejo estilo funcional como STL acumula basta, siempre que sea utilizable * hoy * ... –

6

me gustaría recomendar la biblioteca P-Stade.Oven para su referencia. Esta es una biblioteca fuertemente reforzada que trabaja en rangos de STL y presenta muchas funciones similares a LINQ, incluidos los equivalentes de .Where, .Select .Skip .Take y .Concat.

6

Estoy trabajando en (C# LINQ) -como la biblioteca de sólo C++ encabezado.

Aquí está: http://code.google.com/p/boolinq/

me gustaría llegar cualquier comentario ...

ACTUALIZACIÓN:

Aquí es nuevo enlace a boolinq 2.0: https://github.com/k06a/boolinq

Todo el código fuente se basa en un único archivo de encabezado - https://github.com/k06a/boolinq/blob/master/boolinq/boolinq.h

Es súper corto: menos de 800 líneas durante aproximadamente 60 diferentes operaciones!

+0

la biblioteca se ve muy bien y es la más cercana que he visto a algo que funciona como se esperaba. Sin embargo, no está construyendo para mí en gcc 4.7 con C++ 11 habilitado – lurscher

+0

@lurscher, esta biblioteca fue desarrollada usando Visual C++.Pero me gustaría hacerlo tan portátil y multiplataforma como sea posible. He intentado apoyar a mingw 4.4, pero no es compatible con C++ 11 lambdas. Añadiré soporte de gcc pronto. O puede ayudarme) – k06a

3

Con Boost.Range y Linq en C++ 11, puede escribir consultas LINQ de una manera muy similar: la salida

std::vector<int> numbers = { 1, 2, 3, 4 }; 
auto r = LINQ(from(x, numbers) where(x > 2) select(x * x)); 
for (auto x : r) printf("%i\n", x); 

Will:

9 
16 
+0

'from' y' where' son macros? – CodesInChaos

+0

no, no lo son. La única macro es la macro 'LINQ'. El resto se analiza usando el preprocesador. –

2

Aquí es otra alternative que es simplemente una envoltura alrededor de los algoritmos stl y boost, y así obtienes todos los beneficios de rendimiento de esas implementaciones.

funciona así:

std::vector<int> xs; 
auto count = from(xs) 
    .select([](int x){return x*x;}) 
    .where([](int x){return x > 16;}) 
    .count(); 
auto xs2 = from(xs) 
    .select([](int x){return x*x;}) 
    .to<std::vector<int>>(); 

Tenga en cuenta que algunos métodos devuelven un proxy para rangos de vacío, por ejemplo,

std::vector<int> xs; 
auto max = from(xs) 
    .select([](int x){return x*x;}) 
    .where([](int x){return x > 16;}) 
    .max() 
    .value_or(0); 
+0

Bien hecho, pero algunas cosas: 'void for_each (...) {return ...; } 'no suena bien, y cada vez que tienes' auto x (...) -> decltype (alguna expresión larga) 'y necesitas reutilizar ese tipo dentro de' x' (digamos, 'seleccionar',' invertir ',' take', etc.), simplemente diga 'decltype (x (...))' y pase todos los argumentos, para muchas cosas será * mucho * más corto. Además, tienes 'to_vector', no' a '. :) – Xeo

+0

Xeo: Gracias por los comentarios. Se corrigieron 'to' y' for_each'. Aunque no entiendo muy bien cómo obtener tu sugerencia para acortar el 'decltype's para que funcione. ¿Podría darme un ejemplo más concreto? – ronag

+0

En retrospectiva, me pregunto por qué necesita reutilizar el tipo en absoluto en el cuerpo, ya que el constructor de 'linq_range' no es' explicit'. – Xeo