F#List SelectMany

时间:2011-01-04 23:56:35

标签: f# linq

这是一个非常简单的问题,但我找不到答案:

F#中是否有任何Seq / List操作与LINQ SelectMany匹配?

  • 我知道我可以在F#中使用System.Linq 想要。
  • 我知道我可以做一个递归方法 并使用F#计算表达式 (并制作更强大的东西)。

但是如果我试图证明F#List操作比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

5 个答案:

答案 0 :(得分:30)

collect是SelectMany的F#等价物,但它不提供所有重载。以下是如何制作您引用的那个。

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重载,因为它们会向库中添加噪声。以下是Microsoft Naming中SelectMany的所有四次重载。

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#List操作比LINQ更强大......”虽然seq / list操作非常棒,但真正的“F#power”来自Function CompositionCurrying

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

答案 1 :(得分:11)

您可以使用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 };

答案 2 :(得分:5)

其他帖子显示如何匹配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#List   操作比强大   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#很简单。

答案 3 :(得分:4)

Seq.bind就是你想要的。 SelectMany实际上只是一个monadic bind :)。

所以你要这样做:

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

答案 4 :(得分:0)

创建您自己的 SelectMany 有很多不错的选择,但是直接使用 SelectMany 怎么样?

let flatten (source : 'T seq seq) :'T seq =
    System.Linq.Enumerable.SelectMany(source, id)

这是使用 F# id function 对 .net SelectMany 的基本调用。