【问题标题】:How do you generate all permutations of a list with repetition in a functional programming language?如何在函数式编程语言中生成具有重复的列表的所有排列?
【发布时间】:2020-09-28 09:43:52
【问题描述】:

我正在尝试用函数式编程语言自学一些编程,最近偶然发现了从长度为 n 的列表中生成所有长度为 m 的排列的问题, 重复。从数学上讲,这应该导致总共有n^m 可能的排列,因为每个m 'slots' 都可以用任何n 元素填充。然而,我目前拥有的代码并没有 给我所有的元素:

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 元素的列表中取出一个元素,将其与其余元素放在所有组合的前面,并将结果连接到一个列表中,仅给出n C m 结果。

例如,permuts 2 [1;2;3] 的输出结果

[[1;1]; [1;2]; [1;3]; [2;2]; [2;3]; [3;3]]

而我真正想要的

[[1;1]; [1;2]; [1;3]; [2;1]; [2;2]; [2;3]; [3;1]; [3;2]; [3;3]]

——一共9个元素。如何修复我的代码以获得所需的结果?任何指导表示赞赏。

【问题讨论】:

  • AFAIK 您的示例在 OCaml 中。为什么要添加 haskell 和 scala 标签?
  • 关于似乎是输入列表的 N 次方笛卡尔积的 Haskell 版本,您可以查看 closely related SO question。如果事情没有按正确的顺序调用,请注意过多的内存消耗。
  • 对不起,我是新手。我添加了网站建议的所有标签,但我删除了不相关的标签

标签: functional-programming ocaml permutation


【解决方案1】:

您的错误出现在第二行:

  | n, h :: t -> List.map (fun tt -> h::tt) (permuts (n-1) list)
                 @ permuts n t

确实,这样你正在分解一组 n 元组,其中 k 个元素作为总和

  • 以第一个元素为前缀的 (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) [[]]

【讨论】:

    【解决方案2】:

    在 Haskell 中,使用列表推导很自然地做到这一点:

    samples :: Int -> [a] -> [[a]]
    samples 0 _ = [[]]
    samples n xs =
      [ p : ps
      | p <- xs
      , ps <- samples (n - 1) xs
      ]
    

    【讨论】:

    • 感谢您的评论,但很遗憾,我无法关注您的列表理解部分。您能否详细说明一下,也许用伪代码?
    【解决方案3】:

    在我看来,您永远不想在列表的尾部递归,因为您的所有选择都来自整个列表。

    @dfeuer 的 Haskell 代码看起来不错。请注意,它永远不会解构列表xs。它只是在n 上递归。

    您应该能够使用 List.map 代替列表解析的前两行复制 Haskell 代码,并使用 (n - 1) 代替下一行的递归调用。

    【讨论】:

    • 感谢您的评论,但我无法正确翻译 @dfeur 的建议。特别是,您能解释一下前两行是做什么的,以及它与我上面的地图实现有何不同?
    • 你的地图不错。这是递归不太好。无论如何,这就是我要指出的:-)
    • 我很抱歉,但我认为我确实使用“permuts (n - 1) list”短语在 n 上递归。我仍然不明白为什么我没有得到所有的排列......
    【解决方案4】:

    以下是我在 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 也很常见且惯用,您可以使用它来收集哈希表的键和值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-14
      • 2023-03-27
      • 1970-01-01
      • 2010-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多