如何从F#调用Enumerable.Join?

时间:2010-09-23 10:31:20

标签: c# linq f#

我有两个序列(元组),我需要在其中进行连接:

  • Seq 1:[(City1 * Pin1),(City2 * Pin2),(City1 * Pin3),(City1 * Pin4)]
  • Seq 2:[(Pin1 * ProductA),(Pin2 * ProductB),(Pin1 * ProductC),(Pin2 * ProductA)]

进入序列(元组):

  • [(City1 * ProductA),(City2 * ProductB),(City * ProductC),(City2 * Product A)...]

在C#中,我可以使用Linq Join扩展方法执行此操作,如:

seq1.Join(seq2, t => t.Item2, t=> t.Item1,
    (t,u) => Tuple.Create(t.Item1, u.Item2))

如何在F#中完成此操作?我在那里找不到Seq的加入。

3 个答案:

答案 0 :(得分:6)

编辑:实际上,您可以使用LINQ:

> open System.Linq;;
> let ans = seq1.Join(seq2, (fun t -> snd t), (fun t -> fst t), (fun t u -> (fst t, snd u)));;

为什么不使用F#的原生Seq功能?如果你看at the docsat this question,你可以简单地使用这些而不是LINQ。以Seq.map2函数为例:

> let mapped = Seq.map2 (fun a b -> (fst a, snd b)) seq1 seq2;;

val it : seq<string * string> =
  seq [("city1", "product1"); ("city2", "product2")]

应该为您提供所需内容,其中seq1seq2是您的第一和第二序列。

答案 1 :(得分:3)

F#交互式会话:

> let seq1 = seq [("city1", "pin1"); ("city2", "pin2")];;

val seq1 : seq<string * string> = [("city1", "pin1"); ("city2", "pin2")]

> let seq2 = seq [("pin1", "product1"); ("pin2", "product2")];;

val seq2 : seq<string * string> = [("pin1", "product1"); ("pin2", "product2")]

> Seq.zip seq1 seq2;;
val it : seq<(string * string) * (string * string)> =
  seq
    [(("city1", "pin1"), ("pin1", "product1"));
     (("city2", "pin2"), ("pin2", "product2"))]
> Seq.zip seq1 seq2 |> Seq.map (fun (x,y) -> (fst x, snd y));;
val it : seq<string * string> =
  seq [("city1", "product1"); ("city2", "product2")]

此外,您必须能够对序列使用Linq查询,只需确保您具有对System.Linq程序集的引用并打开名称空间open System.Linq

UPDATE:在复杂场景中,您可以按如下方式使用序列表达式:

open System

let seq1 = seq [("city1", "pin1"); ("city2", "pin2"); ("city1", "pin3"); ("city1", "pin4")]
let seq2 = seq [("pin1", "product1"); ("pin2", "product2"); ("pin1", "product3"); ("pin2", "product1")]

let joinSeq = seq { for x in seq1 do
                        for y in seq2 do
                            let city, pin = x
                            let pin1, product = y
                            if pin = pin1 then
                                yield(city, product) }
for(x,y)in joinSeq do
    printfn "%s: %s" x y

Console.ReadKey() |> ignore

答案 2 :(得分:3)

我认为你所期待的结果并不完全清楚,所以答案有点令人困惑。您的示例可以通过两种方式解释(无论是压缩还是加入),它们都是截然不同的。

  • 压缩:如果您有两个相同长度的列表,并且您想对齐对应的项目(例如,第一个列表中的第一个项目与第二个列表中的第一个项目;第二个列表中的第一个项目第一个列表中的项目,第二个列表中的第二个项目等等。),然后查看使用List.zipList.map2的答案。

    但是,这意味着列表按引脚排序,引脚是唯一的。在这种情况下,您不需要使用Join,甚至在C#/ LINQ中,您也可以使用Zip扩展方法。

  • 加入:如果列表可能有不同的长度,但引脚可能没有排序或不唯一,那么您需要编写一个真正的连接。 Artem K的代码的简化版本如下所示:

    seq { for city, pin1 in seq1 do 
            for pin2, product in seq2 do 
              if pin1 = pin2 then yield city, product }
    

    这可能比LINQ中的Join效率低,因为它会遍历seq2seq1O(seq1.Length * seq2.Length)中每个项目的所有项目,因此复杂度为Join。我不确定,但我认为Join可以使用一些哈希来提高效率。我可能会定义一个小帮手,而不是直接使用open System.Linq module Seq = let join (seq1:seq<_>) seq2 k1 k2 = seq1.Join(seq2, (fun t -> k1 t), (fun t -> k2 t), (fun t u -> t, u)) 方法:

    (seq1, seq2) 
       ||> Seq.join snd fst 
       |> Seq.map (fun (t, u) -> fst t, snd u)
    

    然后你可以这样写:

    zip

最后,如果您知道每个产品只有一个独特的城市(序列长度相同且两个引脚都是唯一的),那么您可以按引脚对两个序列进行排序,然后使用{{1 - 这可能比使用join更有效(特别是如果你可以保持序列从一些早期的操作中排序)。