以下代码(对不起,我不记得我从哪里复制了它)计算了两个可能属于不同类型的列表的笛卡尔(或外)产品:
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中,有几种解决方案可以解决所有列表具有相同类型的问题(在这种情况下,参数是列表列表)。但我还没有看到一个列表有不同类型的例子。
有什么建议吗?
答案 0 :(得分:3)
从理论上讲,你无法做到你想要做的事情,但你可以非常接近它。您无法创建此类函数的原因是静态类型。
使用元组,您可以组合不同类型的值,但为了确保类型安全,元组必须是固定大小的,并且必须知道每个元素的类型。
列表可以包含可变数量的元素,但由于这个原因,每个元素的类型必须相同。否则你无法使用静态类型语言。
在动态类型语言中,您可以创建一个单独的函数,该函数获取列表(A)和另一个列表(B)的列表。然后,将B中的每个元素添加到A中的每个列表中,您就完成了。你也可以用静态类型语言做同样的事情,有两个想法:
object
。第一个想法意味着你需要大量的向下和向上转换,这通常不是你想要的静态类型语言。第二种方法有效,但您必须将每个列表转换为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问题,可能无法编写一个以任何长度的元组作为参数的函数。
很久以前就问过这个问题,我不确定F#是否有任何更新和更改可以使它成为可能。