在OCaml中懒惰的"n选择k"

Sur*_*tor 8 ocaml functional-programming lazy-evaluation list-manipulation

作为枚举集合的更大问题的一部分,我需要编写一个OCaml函数'choose',它接受一个列表并输出作为由该列表的元素组成的所有可能的大小为k的序列的列表(不重复序列,可以通过排列获得彼此).它们放在结束列表中的顺序无关紧要.

例如,

choose 2 [1;2;3;4] = [[1;2];[1;3];[1;4];[2;3];[2;4];[3;4]]
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

我想让整个事情变得懒惰,输出一个懒惰的列表,但是如果你有一个严格的解决方案,那也将非常有用.

Pas*_*uoq 9

这是一个严格且次优的版本.我希望很清楚.它通过假设输入列表中没有重复项,并且仅生成与原始列表中的顺序相同的子列表来避免重复.

长度计算可以通过传递l长度作为参数来计算choose.这会使代码的可读性降低,但效率更高.

对于懒人版本,在代码上撒上"懒惰"和"Lazy.force"......

let rec choose k l =
  if k = 0 
  then [ [] ]
  else
    let len = List.length l in
    if len < k
    then []
    else if k = len
    then [ l ]
    else
      match l with
      h :: t ->
          let starting_with_h =
            (List.map (fun sublist -> h :: sublist) (choose (pred k) t))
          in
          let not_starting_with_h = choose k t in
          starting_with_h @ not_starting_with_h
      | [] -> assert false
;;
  val choose : int -> 'a list -> 'a list list = <fun>

# choose 3 [1; 2; 3; 4; 5; 6; 7] ;;                        
- : int list list =
[[1; 2; 3]; [1; 2; 4]; [1; 2; 5]; [1; 2; 6]; [1; 2; 7]; [1; 3; 4]; [1; 3; 5];
 [1; 3; 6]; [1; 3; 7]; [1; 4; 5]; [1; 4; 6]; [1; 4; 7]; [1; 5; 6]; [1; 5; 7];
 [1; 6; 7]; [2; 3; 4]; [2; 3; 5]; [2; 3; 6]; [2; 3; 7]; [2; 4; 5]; [2; 4; 6];
 [2; 4; 7]; [2; 5; 6]; [2; 5; 7]; [2; 6; 7]; [3; 4; 5]; [3; 4; 6]; [3; 4; 7];
 [3; 5; 6]; [3; 5; 7]; [3; 6; 7]; [4; 5; 6]; [4; 5; 7]; [4; 6; 7]; [5; 6; 7]]
Run Code Online (Sandbox Code Playgroud)

编辑:

一个lazy_list_append显示为从下面的评论中必要的:

type 'a node_t =             
      | Empty
      | Node of 'a * 'a zlist_t
and 'a zlist_t = 'a node_t lazy_t

let rec lazy_list_append l1 l2 =
  lazy 
    (match Lazy.force l1 with
      Empty -> Lazy.force l2 
    | Node (h, lt) ->
    Node (h, lazy_list_append lt l2))
;;
Run Code Online (Sandbox Code Playgroud)


Dan*_*kov 8

使用Haskell解决方案再次插入(因为它们是内置的,所以更容易使用惰性列表):

combinations 0 _ = [[]]
combinations k [] = []
combinations k (x:xs) = map (x:) (combinations (k-1) xs) ++ combinations k xs
Run Code Online (Sandbox Code Playgroud)

前两种情况遵循二项式系数的性质,更具体地说:n choose 0 = 1对所有n包括n=0(这就是它首先处理这种情况的原因0 choose 0).另一个是0 choose k = 0.第三个等式是组合的递归定义的精确转换.

不幸的是,当你将它应用于无限列表时,它返回一个简单的解决方案:

> take 10 $ combinations 3 [1..]
[[1,2,3],[1,2,4],[1,2,5],[1,2,6],[1,2,7],[1,2,8],[1,2,9],[1,2,10],[1,2,11],[1,2,12]]
Run Code Online (Sandbox Code Playgroud)

编辑:好的,所以我们真的想通过有限的步骤来完成每个组合.使用上面的版本我们显然只使用左边的表达式只++生成从1开始的组合.我们可以通过定义一个有趣的列表压缩函数来解决这个问题,该函数通过交替选择每个参数的头来构建列表列表(在第二个参数中非严格是很重要的):

merge [] ys = ys
merge (x:xs) ys = x:merge ys xs
Run Code Online (Sandbox Code Playgroud)

并使用它而不是++:

combinations k (x:xs) = map (x:) (combinations (k-1) xs) `merge` combinations k xs
Run Code Online (Sandbox Code Playgroud)

让我们来看看:

> let comb_10_3 = combinations 3 [1..10]
> let comb_inf_3 = combinations 3 [1..]
> take 10 comb_inf_3 
[[1,2,3],[2,3,4],[1,3,4],[3,4,5],[1,2,4],[2,4,5],[1,4,5],[4,5,6],[1,2,5],[2,3,5]]
> comb_10_3 `intersect` comb_inf_3 == comb_10_3 
True
> last $ combinations 3 [1..10]
[6,8,10]
> elemIndex [6,8,10] $ combinations 3 [1..]
Just 351
Run Code Online (Sandbox Code Playgroud)

所有10 choose 3组合都在那里!