在OCaml的m桶中的n个球的列表

时间:2014-02-08 21:52:09

标签: recursion ocaml permutation

我试图找到n个球散布到m桶中的所有排列。我正在通过递归接近它,但我对我应该递归的事情感到困惑,因为n可以减少任何数字......(我在m-1上递归)有关如何用函数式语言方法做到这一点的任何想法?< / p>

C ++中有一个解决方案,但我不懂C ++。 List of combinations of N balls in M boxes in C++

2 个答案:

答案 0 :(得分:2)

无需生成冗余结果。下面的代码有点难看,但它完成了这项工作:

let ( <|> ) s e = 
  let rec aux s e res = 
    if e - s < 0 then res
    else aux (s + 1) e (s :: res) in
  List.rev (aux s e [])

let rec generate n m =
  let prepend_x l x = List.map (fun u -> x::u) l in
  if m = 1 then [[n]] 
  else
    let l = List.map (fun p -> prepend_x (generate (n - p) (m - 1)) p) (0 <|> n) in
    List.concat l

这个想法很简单,您希望p::uu表单的所有列表generate (n - p) (m - 1) p 0..n范围超过{{1}}

答案 1 :(得分:1)

let flatten_tail l =
  let rec flat acc = function
    | [] -> List.rev acc
    | hd::tl -> flat (List.rev_append hd acc) tl
  in 
  flat [] l

let concat_map_tail f l =
  List.rev_map f l |> List.rev |> flatten_tail

let rm_dup l =
  if List.length l = 0 then l
  else 
    let sl = List.sort compare l in
    List.fold_left (
      fun (acc, e) x -> if x <> e then x::acc, x else acc,e
    ) ([List.hd sl], List.hd sl) (List.tl sl) |> fst |> List.rev

(* algorithm starts from here *)
let buckets m =
  let rec generate acc m =
    if m = 0 then acc
    else generate (0::acc) (m-1)
  in 
  generate [] m

let throw_1_ball bs =
  let rec throw acc before = function
    | [] -> acc
    | b::tl -> 
      let new_before = b::before in
      let new_acc = (List.rev_append before ((b+1)::tl))::acc in
      throw new_acc new_before tl
  in 
  throw [] [] bs

let throw_n_ball n m = 
  let bs = buckets m in
  let rec throw i acc =
    if i = 0 then acc
    else throw (i-1) (concat_map_tail throw_1_ball acc |> rm_dup)
  in 
  throw n [bs]

上面是正确的代码,它是可怕的,因为我添加了几个实用程序函数,并尽可能使尾部递归。但这个想法非常简单。

以下是算法:

  1. 假设我们有3个桶,最初是[0; 0; 0]。
  2. 如果我们将1个球扔进3个水桶,我们每个都有3个 桶的快照,即[[1; 0; 0]; [0; 1; 0]; [0; 0; 1]]。
  3. 那么如果我们还有1个球,对于上面的每个案例,我们将有3个案例, 所以案件清单有9个案例
  4. 然后,如果我们还有一个球,.....
  5. 通过这种方式,我们将生成3^n个案例,其中许多案例可能是多余的。

    因此,在生成每个案例列表时,我们只删除案例列表中的所有重复项。


     utop # throw_n_ball 3 2;;
    - : int list list = [[0; 3]; [1; 2]; [2; 1]; [3; 0]]   
    
    utop # throw_n_ball 5 3;;
    - : int list list = [[0; 0; 5]; [0; 1; 4]; [0; 2; 3]; [0; 3; 2]; [0; 4; 1]; [0; 5; 0]; [1; 0; 4];[1; 1; 3]; [1; 2; 2]; [1; 3; 1]; [1; 4; 0]; [2; 0; 3]; [2; 1; 2]; [2; 2; 1]; [2; 3; 0]; [3; 0; 2]; [3; 1; 1]; [3; 2; 0]; [4; 0; 1]; [4; 1; 0]; [5; 0; 0]]