F#排列

时间:2009-10-06 14:47:09

标签: f#

我需要在给定列表上生成排列。我设法像这样做了

let rec Permute (final, arr) = 
    if List.length arr > 0 then
        for x in arr do
            let n_final = final @ [x]
            let rest = arr |> List.filter (fun a -> not (x = a))
            Permute (n_final, rest)
    else
        printfn "%A" final

let DoPermute lst  = 
    Permute ([], lst)

DoPermute lst

此代码存在明显问题。例如,列表元素必须是唯一的。而且,这与我在任何其他语言中生成直接实现时使用的方法相同。有没有更好的方法在F#中实现它。

谢谢!

7 个答案:

答案 0 :(得分:29)

以下是我在书F# for Scientists(第166-167页)中提供的解决方案:

let rec distribute e = function
  | [] -> [[e]]
  | x::xs' as xs -> (e::xs)::[for xs in distribute e xs' -> x::xs]

let rec permute = function
  | [] -> [[]]
  | e::xs -> List.collect (distribute e) (permute xs)

答案 1 :(得分:7)

对于小列表的排列,我使用以下代码:

let distrib e L =
    let rec aux pre post = 
        seq {
            match post with
            | [] -> yield (L @ [e])
            | h::t -> yield (List.rev pre @ [e] @ post)
                      yield! aux (h::pre) t 
        }
    aux [] L

let rec perms = function 
    | [] -> Seq.singleton []
    | h::t -> Seq.collect (distrib h) (perms t)

它的工作原理如下:函数“distrib”将给定元素分布在列表中的所有位置,例如:

distrib 10 [1;2;3] --> [[10;1;2;3];[1;10;2;3];[1;2;10;3];[1;2;3;10]]

函数perms工作(递归)如下:将列表的头部分布在其尾部的所有排列上。

对于大型列表,distrib函数会变慢,因为它大量使用@运算符,但对于合理长度的列表(< = 10),上面的代码工作正常。

一个警告:如果您的列表包含重复项,则结果将包含相同的排列。例如:

perms [1;1;3] = [[1;1;3]; [1;1;3]; [1;3;1]; [1;3;1]; [3;1;1]; [3;1;1]]

关于这段代码的好处是它返回一系列排列,而不是一次性生成所有排列。

当然,使用命令式基于数组的算法生成排列会(更快),但在大多数情况下,这种算法对我很有帮助。

答案 2 :(得分:3)

这取决于你的意思是“更好”。我认为这稍微优雅一点,但这可能是一个品味问题:

(* get the list of possible heads + remaining elements *)
let rec splitList = function
| [x] -> [x,[]]
| x::xs -> (x, xs) :: List.map (fun (y,l) -> y,x::l) (splitList xs)

let rec permutations = function
| [] -> [[]]
| l -> 
    splitList l 
    |> List.collect (fun (x,rest) ->
         (* permute remaining elements, then prepend head *)
         permutations rest |> List.map (fun l -> x::l))

这可以处理具有重复元素的列表,但它会导致重复的排列。

答案 3 :(得分:3)

这是另一个基于序列的版本,希望比投票的答案更具可读性。 此版本在逻辑方面类似于Jon的版本,但使用计算表达式而不是列表。第一个函数计算在列表l中插入元素x的所有方法。第二个函数计算排列。 您应该能够在较大的列表中使用它(例如,对一组输入的所有排列进行暴力搜索)。

let rec inserts x l =
  seq { match l with
        | [] -> yield [x]
        | y::rest ->
            yield x::l
            for i in inserts x rest do
              yield y::i
      }

let rec permutations l =
  seq { match l with
        | [] -> yield []
        | x::rest ->
            for p in permutations rest do
              yield! inserts x p
      }

答案 4 :(得分:3)

根据Cyrl的建议精神,这里是一个序列理解版本

let rec permsOf xs =
  match xs with
  | [] -> List.toSeq([[]])
  | _ -> seq{ for x in xs do
               for xs' in permsOf (remove x xs) do
                 yield (x::xs')}

其中remove是一个从列表中删除给定元素的简单函数

let rec remove x xs =
  match xs with [] -> [] | (x'::xs')-> if x=x' then xs' else x'::(remove x xs')

答案 5 :(得分:1)

恕我直言,最好的解决方案应该可以减轻F#是一种函数式语言这一事实​​,因此解决方案应该尽可能接近我们所说的排列定义。 因此,置换是这样的事物列表的实例,其中列表的头部以某种方式被添加到输入列表的其余部分的排列中。 erlang解决方案以一种漂亮的方式显示:

permutations([]) -> [[]];
permutations(L) -> [[H|T] H<- L, T <- permutations( L--[H] ) ].

采取“编程erlang”一书

有一个列表理解运算符使用,在这里由同伴stackoverflowers提到的解决方案中有一个帮助函数,它做类似的工作 基本上我投票给没有任何可见循环等的解决方案,只是纯函数定义

答案 6 :(得分:0)

我好像晚了 11 年,但还是以防万一有人需要像我最近所做的那样排列。这是置换函数的 Array 版本,我相信它的性能更高:

    [<RequireQualifiedAccess>]
    module Array =

        let private swap (arr: _[]) i j =
            let buf = arr.[i]
            arr.[i] <- arr.[j]
            arr.[j] <- buf

        let permutations arr =
            match arr with
            | null | [||] -> [||]
            | arr ->
                let last = arr.Length - 1
                let arr = Array.copy arr
                let rec perm arr k =
                    let arr = Array.copy arr
                    [|
                        if k = last then
                            yield arr
                        else
                            for i in k .. last do
                                swap arr k i
                                yield! perm arr (k + 1)
                    |]
                perm arr 0