OCaml 列表的笛卡尔(外)积

Cartesian (outer) product of lists in OCaml

我想遍历列表列表中元素的所有组合,这些列表具有相同的长度但不一定是相同的类型。这就像两个列表的笛卡尔积(在 OCaml 中很容易做到),但对于任意数量的列表。

首先,我尝试编写一个通用的笛卡尔(外)乘积函数,该函数采用列表列表和 returns 元组列表,但这行不通,因为列表的输入列表不会有相同类型的元素。

现在我归结为

类型的函数
'a list * 'b list * 'c list -> ('a * 'b * 'c) list

不幸的是,这将输入的数量固定为三个(例如)。这是

let outer3 (l1, l2, l3) =
   let open List in
   l1 |> map (fun e1 -> 
       l2 |> map (fun e2 ->
           l3 |> map (fun e3 ->
               (e1,e2,e3))))
   |> concat |> concat

这可行,但很麻烦,因为必须为每个输入数量重做。有更好的方法吗?

背景:我想将生成的平面列表提供给 Parmap.pariter

为了解决任意 ntuple 的任务,我们需要使用存在类型。我们可以使用 GADT,但默认情况下它们是关闭的。当然我们可以使用开放变体,但我更喜欢使用第一个 class 模块的语法更繁琐但更便携的解决方案(它之所以有效,是因为 GADT 可以通过第一个 class 模块成为 expressed) .但是足够的理论,首先我们需要一个函数来为我们生成 n_cartesian_product,类型为 'a list list -> 'a list list

let rec n_cartesian_product = function
  | [] -> [[]]
  | x :: xs ->
    let rest = n_cartesian_product xs in
    List.concat (List.map (fun i -> List.map (fun rs -> i :: rs) rest) x)

现在我们需要将不同的类型归为一个类型'a,而存在类型来了,让我们定义一个签名:

module type T = sig
  type t
  val x : t
end

现在让我们尝试为这个存在写一个提升器:

let int x  = (module struct type t = int let x = x end : T)

它有类型:

int -> (module T)

让我们用更多案例扩展示例:

let string x = (module struct type t = string let x = x end : T)
let char x = (module struct type t = char let x = x end : T)

let xxs = [
  List.map int [1;2;3;4];
  List.map string ["1"; "2"; "3"; "4"];
  List.map char ['1'; '2'; '3'; '4']
]

# n_cartesian_product xxs;;
- : (module T) list list =
[[<module>; <module>; <module>]; [<module>; <module>; <module>];
 [<module>; <module>; <module>]; [<module>; <module>; <module>];
 ...

如果您的类型要求允许(例如,如果您不需要公开类型 t),您可以使用其他抽象,如对象或函数,而不是第一个 class 模块。当然,我们的existential很简洁,也许你会需要扩展签名。

我使用了@ivg 的答案,但使用的是带有 GADT 的版本。我在这里复制它以供参考。在输入列表中只能出现类型 floatint 的简单情况下,首先设置

type wrapped = Int : int -> wrapped | Float : float -> wrapped

这是一个没有类型参数的 GADT。那么

let wrap_f f = Float f
let wrap_i i = Int f

将类型包装成求和类型。在 wrapped 值列表中,我们可以从 @ivg 的回答中调用 n_cartesian_product。结果是一个列表 combinations: wrapped list list,它是扁平的(对于当前目的)。

现在要使用 Parmap,我有例如辅助函数 work : float * int * float * float -> float。为了从包装器中获取参数,我进行了模式匹配:

combinations |> List.map (function 
 | [Float f1; Int i; Float f2; Float f3] -> (f1, i, f2, f3)
 | _ -> raise Invalid_argument "wrong parameter number or types")

构建平面元组列表。这最终可以通过 worker 函数 work 馈送到 Parmap.pariter

此设置几乎与使用常规求和类型 type wrapsum = F of float | I of int 而不是 wrapped 相同。模式匹配是一样的;唯一的区别似乎是输入错误,例如(F 1, I 1, F 2.0, F, 3.0) 只会在运行时检测到,而不是像这里那样在编译时检测到。