给出一个列表,并输出所有排列的列表。
这是我的想法:
递归地,hd::tl
的排列是将hd
分发到tl
的每个排列列表中。通过分发hd
,我的意思是将hd
插入列表中的每个可能的插槽中。
例如,将1
分发到[2;3]
会生成[[1;2;3];[2;1;3];[2;3;1]]
。
所以这是代码
let distribute c l =
let rec insert acc1 acc2 = function
| [] -> acc2
| hd::tl ->
insert (hd::acc1) ((List.rev_append acc1 (hd::c::tl)) :: acc2) tl
in
insert [] [c::l] l
let rec permutation = function
| [] -> [[]]
| hd::tl ->
List.fold_left (fun acc x -> List.rev_append (distribute hd x) acc) [] (permutation tl)
我猜上面的代码是正确的。
我的问题是:在OCaml中编写排列是否有更好的方法(在简单性,性能,空间效率等方面)?
答案 0 :(得分:5)
其他方法包括a)对于每个元素,将其添加到列表中除元素之外的每个排列,或者b)从每个元素生成排列!指数。请参阅Rosetta Code示例。我怀疑它们具有相似的空间复杂性。
答案 1 :(得分:1)
这是一种生成排列的流(即,惰性列表)的解决方案。这避免了必须立即将整个列表保存在内存中。如果您只需要有限数量的排列,它可以让您使用更长的列表。
为了简单起见,我使用Jane Street Core作为我的基础库。
open Core.Std
let permutations lst =
let lstar = Array.of_list lst in
let len = Array.length lstar in
let ks = List.range 1 (len + 1) in
let indices = Int.Set.of_list (List.range 0 len) in
let choose k (v, indices, res) =
let ix = Option.value_exn (Int.Set.find_index indices (v mod k)) in
(v / k, Int.Set.remove indices ix, lstar.(ix) :: res)
in
let perm i =
let (v, _, res) =
List.fold_right ks ~f: choose ~init: (i, indices, [])
in
if v > 0 then None else Some res
in
Stream.from perm
以下是此实施的会话:
# let s = permutations ['a'; 'b'; 'c'; 'd'];;
val s : char list Stream.t = <abstr>
# Stream.npeek 100 s;;
- : char list list =
[[d; c; b; a]; [d; c; a; b]; [d; b; a; c]; [c; b; a; d]; [d; b; c; a];
[d; a; c; b]; [d; a; b; c]; [c; a; b; d]; [c; b; d; a]; [c; a; d; b];
[b; a; d; c]; [b; a; c; d]; [c; d; b; a]; [c; d; a; b]; [b; d; a; c];
[b; c; a; d]; [b; d; c; a]; [a; d; c; b]; [a; d; b; c]; [a; c; b; d];
[b; c; d; a]; [a; c; d; b]; [a; b; d; c]; [a; b; c; d]]
# let s = permutations (List.range 0 10);;
val s : int list Stream.t = <abstr>
# Stream.iter ignore s;;
- : unit = ()
# Stream.count s;;
- : int = 3628800
# fact 10;;
- : int = 3628800
关键的想法是,您可以通过一种方式对排列进行编号,从而可以轻松地从索引中提取排列本身。直觉类似于对整数的十进制表示进行编号的方式。要从索引中提取4位数字的十进制表示,您可以执行以下操作:
let digits i =
let ks = [10;10;10;10] in
let extract k (v, result) = (v / k, v mod k :: result) in
let (_, res) = List.fold_right ks ~f: extract ~init: (i, []) in
res
请注意,这每次都使用10作为除数。要提取排列,除了使用n,n - 1,n - 2,...,1作为除数之外,你会做一些非常相似的事情。
答案 2 :(得分:0)
你所拥有的基本上是一种“基于插入”的方法 - 你按顺序浏览输入列表,然后以各种可能的方式将每个后续输入元素“插入”到部分结果中。
您可以采用的另一种方法是“基于选择”的方法 - 您可以通过从未使用的值中“选择”每种可能性来构建结果列表的每个后续元素。形式上,置换具有来自输入列表的任何元素的第一元素,并且第一元素之后的置换部分来自输入列表的“其余”的任何排列。这会生成所有排列。
棘手的部分是如何获得“列表的其余部分”。最简单的方法是编写一个“删除”函数,它接受一个值和一个列表,并返回列表的副本,而第一个元素不等于给定的元素。但是,对于不具有所有类型相等性的语言,或者您希望区分不同但相同的元素的语言,这会遇到问题。更正确的方法是在迭代列表时同时生成元素和“列表的其余部分”。