问题描述
我正在尝试以一种功能性编程语言自学一些编程,最近偶然发现了从长度m
列表生成长度n
的所有排列的问题,重复。从数学上讲,这将导致总共n^m
个可能的排列,因为每个m
'slot'都可以填充任何n
元素。但是,我目前拥有的代码 not 没有给我所有元素:
let rec permuts n list =
match n,list with
0,_ -> [[]]
| _,[] -> []
| n,h :: t -> (List.map (fun tt -> h::tt) (permuts (n-1) list))
@ permuts n t;;
该算法基本上从具有m
个元素的列表中取出一个元素,并将其与其余元素一起拍在所有组合的最前面,然后将结果连接到一个列表中,仅给出{{1 }}结果。
例如,n C m
的输出会产生
permuts 2 [1;2;3]
我真正想要的
[[1;1]; [1;2]; [1;3]; [2;2]; [2;3]; [3;3]]
-总共9个元素。如何修复我的代码,以便获得所需的结果?任何指导表示赞赏。
解决方法
您的错误出现在以下内容的第二行:
| n,h :: t -> List.map (fun tt -> h::tt) (permuts (n-1) list)
@ permuts n t
实际上,您正在分解以k个元素为和的n个元组的集合
- 以第一个元素为前缀的(n-1)个元组的集合
- 具有(k-1)个元素的n元组的集合
从三组的基数来看,自从出现明显的不匹配
k^n ≠ k^(n-1) + (k-1)^n
问题是第二个术语不合适。 为避免此问题,最好编写几个辅助函数。 我建议编写以下三个辅助函数:
val distribute: 'a list -> 'a list -> 'a list list
(** distribute [x_1;...;x_n] y returns [x_1::y;...x_n::y] *)
val distribute_on_all: 'a list -> 'a list list
(** distribute_on_all x [l_1;...;l_n] returns distribute x l_1 @ ... @ distribute x l_n *)
val repeat: int -> ('a -> 'a) -> 'a -> 'a
(** repeat n f x is f(...(f x)...) with f applied n times *)
那么您的功能将很简单
let power n l = repeat n (distribute_on_all l) [[]]
,
在Haskell中,使用列表推导很自然地做到这一点:
samples :: Int -> [a] -> [[a]]
samples 0 _ = [[]]
samples n xs =
[ p : ps
| p <- xs,ps <- samples (n - 1) xs
]
,
在我看来,您永远都不想递归列表的尾部,因为您的所有选择均来自整个列表。
@dfeuer的Haskell代码看起来正确。请注意,它永远不会解构列表xs
。它只是在n
上递归。
您应该能够使用List.map
代替列表理解的前两行来复制Haskell代码,并使用(n - 1)
代替下一行来进行递归调用。
这是我在OCaml中编写的方式:
let perm src =
let rec extend remaining_count tails =
match remaining_count with
| 0 -> tails
| _ ->
(* Put an element 'src_elt' taken from all the possible elements 'src'
in front of each possible tail 'tail' taken from 'tails',resulting in 'new_tails'. The elements of 'new_tails' are one
item longer than the elements of 'tails'. *)
let new_tails =
List.fold_left (fun new_tails src_elt ->
List.fold_left (fun new_tails tail ->
(src_elt :: tail) :: new_tails
) new_tails tails
) [] src
in
extend (remaining_count - 1) new_tails
in
extend (List.length src) [[]]
List.fold_left
调用可能看起来有些吓人,但效果很好。因此,练习使用List.fold_left
是一个好主意。同样,Hashtbl.fold
也是常见和惯用的,您将使用它来收集哈希表的键和值。