F # List SelectMany - PullRequest
       2

F # List SelectMany

29 голосов
/ 05 января 2011

Это довольно простой вопрос, но я не нашел ответа:

Есть ли какая-либо операция Seq / List в F #, соответствующая LINQ SelectMany?

  • Я знаю, что могу использовать System.Linq в F #, если я хочу.
  • Я знаю, что могу сделать рекурсивный метод и использовать выражения вычисления F # (и делать еще более сильные вещи).

Но если я попытаюсь доказать, что операции со списком F # более мощные, чем LINQ ...

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

В C # SelectMany синтаксис использования довольно прост:

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

Есть ли простое прямое совпадение, List.flatten, List.bind или что-то в этом роде?

У SelectMany есть пара подписей, но самая сложная из них выглядит так:

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

В терминах F # это будет:

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

Ответы [ 4 ]

30 голосов
/ 05 января 2011

collect - это эквивалент F # для SelectMany, однако он не обеспечивает всех перегрузок.Вот как сделать тот, на который вы ссылались.

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>

Я считаю, что F # не обеспечивает всех перегрузок SelectMany, потому что они добавили бы шум в библиотеку.Вот все четыре перегрузки на SelectMany в 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

"Операции со списком F # более мощные, чем LINQ ..." Хотя операции с последовательностями / списками велики, некоторые настоящие "мощности F #"происходит от Композиция функций и Curry .

// function composition
let collect selector = Seq.map selector >> Seq.concat
11 голосов
/ 05 января 2011

Вы можете использовать List.collect или 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]))

Это будет примерно эквивалентно следующему коду C #:

var flat = from i1 in items1
           from i2 in items2
           select new { i1, i2 };
5 голосов
/ 05 января 2011

Другие сообщения показывают, как сопоставить linq с

Начиная с этого linq:

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

Эквивалентный F #:

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

Два бита кодапримерно эквивалентны по выразительности и сложности.

С учетом сказанного давайте обратимся к конкретному комментарию в вашем посте:

Но если я попытаюсь доказать, что операции со списком F # более мощныеLINQ ...

Операции в модулях Seq / List примерно эквивалентны расширениям Enumerable / Linq.

Однако я бы сказал, что убийственная функция для списков - это возможность образец совпадения на них.Вот глупый пример, который нелегко преобразовать в 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)]

Это было бы немного неудобно для переопределения в C #, но его просто написать F #.

4 голосов
/ 05 января 2011

Seq.bind - это то, что вы хотите.SelectMany - это на самом деле просто монадное связывание:).

Итак, вы бы сделали:

seq { for i in items1 do
         for j in items2 do
            yield ....  };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...