如何计算不同类型的n个列表的笛卡尔积?

时间:2017-05-02 00:13:57

标签: list f# cartesian-product

以下代码(对不起,我不记得我从哪里复制了它)计算了两个可能属于不同类型的列表的笛卡尔(或外)产品:

let outer2 xs ys = 
    xs |> List.collect (fun x -> ys |> List.map (fun y -> x, y))

从这个可以编写一个函数来计算两个列表的外部产品,这两个列表是2元组的元素:

let outerTup tup = outer2 (fst tup) (snd tup)

将此扩展到包含三个列表的元组的情况并不难。但是,我找不到一种方法来编写一个函数来获取任何长度的元组,其元素是列表(可能是不同类型)并计算列表的笛卡尔积。

在SO和F#Snippets中,有几种解决方案可以解决所有列表具有相同类型的问题(在这种情况下,参数是列表列表)。但我还没有看到一个列表有不同类型的例子。

有什么建议吗?

3 个答案:

答案 0 :(得分:3)

从理论上讲,你无法做到你想要做的事情,但你可以非常接近它。您无法创建此类函数的原因是静态类型。

使用元组,您可以组合不同类型的值,但为了确保类型安全,元组必须是固定大小的,并且必须知道每个元素的类型。

列表可以包含可变数量的元素,但由于这个原因,每个元素的类型必须相同。否则你无法使用静态类型语言。

在动态类型语言中,您可以创建一个单独的函数,该函数获取列表(A)和另一个列表(B)的列表。然后,将B中的每个元素添加到A中的每个列表中,您就完成了。你也可以用静态类型语言做同样的事情,有两个想法:

  1. 您首先将列表的每个元素转换为object
  2. 您首先要创建各种类型的识别联盟。
  3. 第一个想法意味着你需要大量的向下和向上转换,这通常不是你想要的静态类型语言。第二种方法有效,但您必须将每个列表转换为DU类型(您还需要创建DU),稍后您需要进行模式匹配。从技术上讲,它只是以类型安全的方式与1.相同。

    另一种方法,我推荐的是使用Applicative。应用程序实际上意味着您升级一个函数,因此函数的每个参数都可以是选项列表等等。因此,您首先要创建一个apply函数,如下所示:

    let apply fs xs = [
        for f in fs do
        for x in xs do
            yield f x
    ]
    let (<*>) = apply
    

    一旦你有这样的功能,你可以这样写:

    [fun a b c d -> (a,b,c,d)]
        <*> [1..5]
        <*> ["a";"b"]
        <*> [(0,0);(1,1)]
        <*> [100;200]
    

    然后返回一个包含以下内容的列表:

    [(1, "a", (0, 0), 100); (1, "a", (0, 0), 200); (1, "a", (1, 1), 100);
     (1, "a", (1, 1), 200); (1, "b", (0, 0), 100); (1, "b", (0, 0), 200);
     (1, "b", (1, 1), 100); (1, "b", (1, 1), 200); (2, "a", (0, 0), 100);
     (2, "a", (0, 0), 200); (2, "a", (1, 1), 100); (2, "a", (1, 1), 200);
     (2, "b", (0, 0), 100); (2, "b", (0, 0), 200); (2, "b", (1, 1), 100);
     (2, "b", (1, 1), 200); (3, "a", (0, 0), 100); (3, "a", (0, 0), 200);
     (3, "a", (1, 1), 100); (3, "a", (1, 1), 200); (3, "b", (0, 0), 100);
     (3, "b", (0, 0), 200); (3, "b", (1, 1), 100); (3, "b", (1, 1), 200);
     (4, "a", (0, 0), 100); (4, "a", (0, 0), 200); (4, "a", (1, 1), 100);
     (4, "a", (1, 1), 200); (4, "b", (0, 0), 100); (4, "b", (0, 0), 200);
     (4, "b", (1, 1), 100); (4, "b", (1, 1), 200); (5, "a", (0, 0), 100);
     (5, "a", (0, 0), 200); (5, "a", (1, 1), 100); (5, "a", (1, 1), 200);
     (5, "b", (0, 0), 100); (5, "b", (0, 0), 200); (5, "b", (1, 1), 100);
     (5, "b", (1, 1), 200)]
    

    如果您不想创建运算符<*>,您也可以写:

    [fun a b c d -> (a,b,c,d)]
        |> apply <| [1..5]
        |> apply <| ["a";"b"]
        |> apply <| [(0,0);(1,1)]
        |> apply <| [100;200]
    

    但我通常不鼓励使用<|。我宁愿这样做:

    let ap xs fs = [
        for f in fs do
        for x in xs do
            yield f x
    ]
    
    [fun a b c d -> (a,b,c,d)]
        |> ap [1..5]
        |> ap ["a";"b"]
        |> ap [(0,0);(1,1)]
        |> ap [100;200]
    

    您必须即时创建的唯一内容是第一行。将四,五,六,......参数映射到元组的函数。

    如果您想了解有关Applicatives及其工作原理的更多信息,我写了两篇关于此主题的博客文章:

    http://sidburn.github.io/blog/2016/04/13/applicative-list http://sidburn.github.io/blog/2016/03/31/applicative-functors

答案 1 :(得分:2)

这不是n的解决方案。通常不希望使用FSharp反射命名空间。

let outer2 xs ys = 
  xs |> List.collect (fun x -> List.map (fun y -> x, y) ys)

let outerTup2 (a,b) = outer2 a b

let outerTup3 (a,b,c) = 
  a
  |> outer2 b
  |> outer2 c
  |> List.map (fun (c,(b,a))->a,b,c)

let outerTup4 (a,b,c,d) =
  a
  |> outer2 b
  |> outer2 c
  |> outer2 d
  |> List.map (fun (d,(c,(b,a)))->a,b,c,d) 

// etc...

outerTup2 ([1;2],[3;4])

outerTup3 ([1;2],[3;4],[5;6])

outerTup4 ([1;2],[3;4],[5;6],[7;8])

答案 2 :(得分:2)

根据这个StackOverflow问题,可能无法编写一个以任何长度的元组作为参数的函数。

Variable length tuples in f#

很久以前就问过这个问题,我不确定F#是否有任何更新和更改可以使它成为可能。