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

How can I calculate the cartesian product of n lists of different types?

下面的代码(对不起,我不记得我从哪里复制的)计算两个可能不同类型的列表的笛卡尔(或外)积:

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

由此可以编写一个函数来计算作为二元组元素的两个列表的外积:

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

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

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

有什么建议吗?

这不是 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])

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

Variable length tuples in f#

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

理论上你不能完全按照你想做的去做,但你可以非常接近它。你不能创建这样一个函数的原因是静态类型。

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

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

例如,在动态类型语言中,您可以创建一个函数,该函数接受一个列表 (A) 和另一个列表 (B)。然后将 B 中的每个元素添加到 A 中的每个列表中,就完成了。您也可以使用两种想法在静态类型语言中执行相同的操作:

  1. 首先将列表中的每个元素转换为 object
  2. 您首先创建一个所有类型的可区分联盟。

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

我推荐的另一种方法是使用 Applicative。一个应用程序实际上意味着你 upgrade 一个函数所以函数的每个参数都可以是一个 option, list等等。所以你首先创建一个像这样的 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]

然后 returns 一个列表包含:

[(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