我需要在给定列表上生成排列。我设法像这样做了
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#中实现它。
谢谢!
答案 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