在我看来,这是一个典型的问题示例,考虑更一般的情况会使问题变得更容易。考虑在l 牌中找出所有n 牌组合的问题。
为了简单起见,我认为cards 只是通用的'a。另外,我不会在牌桌和牌桌之间做任何区分,我只是将两者视为一个列表。将两者分开可能会带来更好的解决方案,但会使其不那么容易理解:-)
因此,您将拥有一个函数comb n l,它返回列表l 中n 元素的所有组合的列表(因此comb 返回'a list list)。让我们递归地做:
首先,如果n大于l的长度,就放弃吧:
exception NotEnoughElement of int*int
let rec comb n l =
let length_l = List.length l in
if n > length_l
raise (NotEnoughElement (n, length_l))
else
现在我们确定n是合适的,根据n有3种情况需要考虑:如果n = 0,则返回一个空列表
match n with
| 0 -> []
如果n 是列表的大小,则别无选择:唯一合适的子列表是...列表本身(当您在桌子上有cards 时,这与您的情况类似)。请注意,我们返回一个包含 l 的列表以履行我们的约定,即 comb 返回一个包含所有子列表的列表。
| _ when n == length_l -> [l]
最后,如果n 小于l 的长度,我们可以选择:要么取l 的头部,要么不取。
| _ ->
如果我们取l 的头部,那么我们必须在l 的尾部搜索n-1 元素的组合,并将l 的头部附加到所有这些子组合中。要进行附加,请使用此功能
let app_head sublist = (List.hd l) :: sublist in
如何在l 的尾部取n-1 元素的组合?好吧,我们有约定说comb n l返回l中所有大小n组合的列表,所以我们可以使用这个“契约”:
let sublists = comb (n-1) (List.tl l) in
现在,让我们进行追加,List 模块提供了一个 map 函数,它将函数应用于列表的所有元素,我们可以使用我们的 app_head 函数:
let combination_head_taken = List.map app_head sublists in
现在,如果我们不取头部,那么我们仍然需要在l 的尾部搜索n 元素。再次,我们在comb 上使用我们的聚合:
let combination_head_not_taken = comb (n-1) (List.tl l) in
然后,只需连接在两个子案例中生成的组合:
combination_head_taken @ combination_head_not_taken
然后你可以玩utop(假设你在comb.ml中写了这个函数):
$ utop
# #use "comb.ml";;
# let hand = [1;2;3;4;5];;
# comb 3 hand;;
- : int list list = [[2; 3; 4; 5]; [1; 3; 4; 5]; [1; 2; 4; 5]; [1; 2; 3; 5]]