2011-01-04 12 views
27

Jest to dość proste pytanie, ale nie mogę znaleźć odpowiedzi:F # Lista SelectMany

Czy istnieje jakakolwiek operacja/Lista seq w F #, aby dopasować LINQ SelectMany?

  • Wiem, że mogę użyć System.Linq w F #, jeśli chcę chce.
  • Wiem, że mogę wykonać metodę rekursywną i użyć wyrażeń obliczeniowych F # (i zrobić jeszcze mocniejsze rzeczy).

Ale jeśli staram się udowodnić, że operacje # Lista F są mocniejsze niż LINQ ...

  • .gdzie = List.filter
  • .Wybrać = List.map
  • .Aggregate = List.fold
  • ...

W składni C# SelectMany użytkowania jest dość prosta:

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

Czy jest jakiś łatwy bezpośredni mecz, List.flatten, List.bind lub coś w tym stylu?

SelectMany ma kilka podpisów, ale najbardziej skomplikowana wydaje się być:

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

w F # warunkach byłoby to:

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

Jak można pokazać, że lista F # jest silniejsza niż lista C#? Jeśli jest to kwestia, która z nich ma więcej funkcji, to nie jest ona silniejsza, po prostu bardziej rozdęta. Czy próbujesz pokazać, że jest coś, co możesz zrobić w F #, którego nie możesz używać z C#, używając list? –

Odpowiedz

26

collect jest odpowiednikiem F # SelectMany, jednak nie zapewnia wszystkich przeciążeń. Oto jak zrobić ten, do którego się odwołujesz.

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> 

Wierzę, że F # nie zapewnia wszystkich przeciążeń SelectMany, ponieważ dodawałyby one szum do biblioteki. Oto cztery przeciążenia dla SelectMany w 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 

"operacje # Lista F są mocniejsze niż LINQ ..." Podczas operacji nast/list są świetne niektóre prawdziwe "F # moc" pochodzi od Function Composition i Currying.

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

Seq.bind to, co chcesz. SelectMany to tak naprawdę monadyczne powiązanie :).

Więc chcesz zrobić:

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

Nie widzę "Seq.indId" w bibliotece F # lub Powerpack. Czy chodziło Ci o "Seq.collect"? – Juliet

+0

Nie widzisz 'Seq.bind', ponieważ nie jest to funkcja w module' Seq'; jest to metoda konstruktora przepływu pracy znajdująca się poniżej monidera 'seq'. – pblasucci

+4

@pblasucci: Zasadnie tak, ale 'seq {..}' nie jest prawdziwym wyrażeniem obliczeniowym. Jest to obsługiwane przez kompilator, a 'seq' nie jest tak naprawdę konstruktorem obliczeniowym (jest to po prostu funkcja' seq <'a> -> seq <'a> '. Więc' Seq.collect' jest właściwą odpowiedzią, ale tak naprawdę nie jest używana w skompilowanej formie wyrażenia sekwencji: –

11

Można użyć List.collect lub 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])) 

To byłoby z grubsza odpowiednik następującego kodu C#:

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

Inne posty pokazać, jak dopasować LINQ z

Począwszy od tego LINQ:

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

Equivalent F # jest:

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 -> ...)) 

Dwa fragmenty kodu są mniej równoważny w ekspresji i złożoności.

Z powiedział, że niech rozwiązać konkretny komentarz w poście:

Ale jeśli staram się dowieść, że F # lista operacje są bardziej wydajne niż LINQ ...

Operations w modułach Seq/List są w przybliżeniu równoważne rozszerzeniom Enumerable/Linq.

Jednakże, powiedziałbym, że funkcja zabójcy dla list to możliwość dopasowania wzoru na. Oto głupi przykład, który nie konwertuje łatwo 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)] 

byłoby to trochę niewygodne reimplement w C#, ale jest martwy prosty napisać F #.