2011-01-04 18 views
27

Esta es la pregunta bastante simple, pero no he encontrado una respuesta:F # Lista SelectMany

¿Hay alguna operación/Sec Lista de F # para que coincida con el LINQ SelectMany?

  • Sé que puedo usar System.Linq en F # si I quiero.
  • Sé que puedo hacer un método recursivo y uso F # Expresiones de cálculo (y hago cosas aún más poderosas).

Pero si trato de demostrar que las operaciones # Lista F son más poderosos que LINQ ...

  • .Where = List.filter
  • .Elija = List.map
  • .Aggregate = List.fold
  • ...

En sintaxis de uso # SelectMany C es bastante simple:

var flattenedList = from i in items1 
        from j in items2 
        select ... 

¿Existe alguna coincidencia directa fácil, List.flatten, List.bind o algo así?

SelectMany tiene un par de firmas, pero el más complejo parece ser:

IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
    this IEnumerable<TSource> source, 
    Func<TSource, IEnumerable<TCollection>> collectionSelector, 
    Func<TSource, TCollection, TResult> resultSelector 
); 

En términos F # esto sería:

('a -> 'b list) -> ('a -> 'b -> 'c) -> 'a list -> 'c list 
+2

¿Cómo demostrarías que la lista F # es más poderosa que la lista C#? Si es una cuestión de cuál tiene más funciones para él, eso no se muestra más poderoso, solo que más hinchado. ¿Estás tratando de demostrar que hay algo que puedes hacer en F # que no puedes con C#, usando listas? –

Respuesta

26

collect es el equivalente F # de SelectMany sin embargo, no proporciona todas las sobrecargas. A continuación, le indicamos cómo hacer el que mencionó.

let selectMany (ab:'a -> 'b seq) (abc:'a -> 'b -> 'c) input = 
    input |> Seq.collect (fun a -> ab a |> Seq.map (fun b -> abc a b)) 
// gives 
// val selectMany : ('a -> seq<'b>) -> ('a -> 'b -> 'c) -> seq<'a> -> seq<'c> 

Creo que F # no proporciona todas las sobrecargas de SelectMany porque agregarían ruido a la biblioteca. Aquí están las cuatro sobrecargas a SelectMany en Microsoft Naming.

let selectMany (source : 'TSource seq) (selector : 'TSource -> 'TResult seq) = 
    source |> Seq.collect selector 

let selectMany (source : 'TSource seq) (selector : 'TSource -> int -> 'TResult seq) = 
    source |> Seq.mapi (fun n s -> selector s n) |> Seq.concat 

let selectMany (source : 'TSource) 
       (collectionSelector : 'TSource -> 'TCollection seq) 
       (resultSelector : 'TSource -> 'TCollection -> 'TResult) = 
    source 
    |> Seq.collect (fun sourceItem -> 
     collectionSelector sourceItem 
     |> Seq.map (fun collection -> resultSelector sourceItem collection)) 

let selectMany (source : 'TSource) 
       (collectionSelector : 'TSource -> int -> 'TCollection seq) 
       (resultSelector : 'TSource -> 'TCollection -> 'TResult) = 
    source 
    |> Seq.mapi (fun n sourceItem -> 
     collectionSelector sourceItem n 
     |> Seq.map (fun collection -> resultSelector sourceItem collection)) 
    |> Seq.concat 

"operaciones # Lista F son más poderosos que LINQ ..." Mientras que las operaciones siguientes/lista son grandes algunos reales "F # poder" viene de Function Composition y Currying.

// function composition 
let collect selector = Seq.map selector >> Seq.concat 
4

Seq.bind es lo que desea. SelectMany es realmente solo un enlace monádico :).

Así que harías:

seq { for i in items1 do 
     for j in items2 do 
      yield .... }; 
+0

No veo 'Seq.bind' en la biblioteca F # ni en el paquete de alimentación. ¿Querías 'Seq.collect'? – Juliet

+0

No ve 'Seq.bind' porque no es una función en el módulo' Seq'; es un método del generador de flujo de trabajo subyacente al monoide 'seq'. – pblasucci

+4

@pblasucci: En principio sí, pero 'seq {..}' no es una expresión de cálculo real. Es manejado especialmente por el compilador y 'seq' no es realmente un constructor de cómputo (es solo una función' seq <'a> -> seq <'a> '. Por lo tanto,' Seq.collect' es la respuesta correcta, pero en realidad no se usa en la forma compilada de la expresión de secuencia. –

11

Usted puede utilizar List.collect o Seq.Collect:

let items1 = [1; 2; 3] 
let items2 = [4; 5; 6] 
let flat = items1 |> List.collect (fun i1 -> items2 |> List.map (fun i2 -> [i1, i2])) 

Eso sería más o menos equivalente al siguiente código C#:

var flat = from i1 in items1 
      from i2 in items2 
      select new { i1, i2 }; 
5

Otros mensajes muestran cómo hacer coincidir la LINQ con

A partir de esta LINQ:

var flattenedList = from i in items1 
        from j in items2 
        select ... 
var flattenedList2 = items1.SelectMany(i => items2.Map(j => ...)) 

Equivalente F # es:

let flattenedList = seq { 
    for a in items1 do 
    for b in items2 do 
     yield ... } 
let flattenedList2 = items1 |> Seq.collect (fun i -> items2 |> Seq.map (fun j -> ...)) 

Los dos bits de código son más o menos equivalente en expresividad y complejidad.

Dicho esto, vamos a abordar un comentario específico en su mensaje:

Pero si trato de demostrar que # Lista F operaciones son más poderosos que LINQ ...

Operaciones en los módulos Seq/List son aproximadamente equivalentes a las extensiones Enumerable/Linq.

Sin embargo, diría que la característica principal de las listas es la capacidad de coincidir con el patrón en ellas. He aquí un ejemplo tonto, que no se convierte fácilmente a LINQ:

let rec funky = function 
    | x::y::z::rest -> (z, y)::funky(z::x::rest) 
    | [y;z]-> [(z, y)] 
    | [z] -> [(z, z)] 
    | [] -> [] 
// funky [1..6] 
// = (int * int) list = [(3, 2); (4, 1); (5, 3); (6, 4)] 

Esto sería un poco incómodo volver a implementar en C#, pero sus muertos sencillo escribir F #.