F# 中的组合和排列
我最近为 F# 项目编写了以下组合和排列函数,但我很清楚它们还远未优化。
/// Rotates a list by one place forward.
let rotate lst =
List.tail lst @ [List.head lst]
/// Gets all rotations of a list.
let getRotations lst =
let rec getAll lst i = if i = 0 then [] else lst :: (getAll (rotate lst) (i - 1))
getAll lst (List.length lst)
/// Gets all permutations (without repetition) of specified length from a list.
let rec getPerms n lst =
match n, lst with
| 0, _ -> seq [[]]
| _, [] -> seq []
| k, _ -> lst |> getRotations |> Seq.collect (fun r -> Seq.map ((@) [List.head r]) (getPerms (k - 1) (List.tail r)))
/// Gets all permutations (with repetition) of specified length from a list.
let rec getPermsWithRep n lst =
match n, lst with
| 0, _ -> seq [[]]
| _, [] -> seq []
| k, _ -> lst |> Seq.collect (fun x -> Seq.map ((@) [x]) (getPermsWithRep (k - 1) lst))
// equivalent: | k, _ -> lst |> getRotations |> Seq.collect (fun r -> List.map ((@) [List.head r]) (getPermsWithRep (k - 1) r))
/// Gets all combinations (without repetition) of specified length from a list.
let rec getCombs n lst =
match n, lst with
| 0, _ -> seq [[]]
| _, [] -> seq []
| k, (x :: xs) -> Seq.append (Seq.map ((@) [x]) (getCombs (k - 1) xs)) (getCombs k xs)
/// Gets all combinations (with repetition) of specified length from a list.
let rec getCombsWithRep n lst =
match n, lst with
| 0, _ -> seq [[]]
| _, [] -> seq []
| k, (x :: xs) -> Seq.append (Seq.map ((@) [x]) (getCombsWithRep (k - 1) lst)) (getCombsWithRep k xs)
有人对如何加速这些函数(算法)有任何建议吗?我对如何改进排列(有重复和没有重复)特别感兴趣。回想起来,涉及列表轮换的业务在我看来效率不太高。
更新
这是我对 getPerms 函数的新实现,灵感来自 Tomas 的答案。
不幸的是,它并不比现有的快。建议?
let getPerms n lst =
let rec getPermsImpl acc n lst = seq {
match n, lst with
| k, x :: xs ->
if k > 0 then
for r in getRotations lst do
yield! getPermsImpl (List.head r :: acc) (k - 1) (List.tail r)
if k >= 0 then yield! getPermsImpl acc k []
| 0, [] -> yield acc
| _, [] -> ()
}
getPermsImpl List.empty n lst
I've recently written the following combinations and permutations functions for an F# project, but I'm quite aware they're far from optimised.
/// Rotates a list by one place forward.
let rotate lst =
List.tail lst @ [List.head lst]
/// Gets all rotations of a list.
let getRotations lst =
let rec getAll lst i = if i = 0 then [] else lst :: (getAll (rotate lst) (i - 1))
getAll lst (List.length lst)
/// Gets all permutations (without repetition) of specified length from a list.
let rec getPerms n lst =
match n, lst with
| 0, _ -> seq [[]]
| _, [] -> seq []
| k, _ -> lst |> getRotations |> Seq.collect (fun r -> Seq.map ((@) [List.head r]) (getPerms (k - 1) (List.tail r)))
/// Gets all permutations (with repetition) of specified length from a list.
let rec getPermsWithRep n lst =
match n, lst with
| 0, _ -> seq [[]]
| _, [] -> seq []
| k, _ -> lst |> Seq.collect (fun x -> Seq.map ((@) [x]) (getPermsWithRep (k - 1) lst))
// equivalent: | k, _ -> lst |> getRotations |> Seq.collect (fun r -> List.map ((@) [List.head r]) (getPermsWithRep (k - 1) r))
/// Gets all combinations (without repetition) of specified length from a list.
let rec getCombs n lst =
match n, lst with
| 0, _ -> seq [[]]
| _, [] -> seq []
| k, (x :: xs) -> Seq.append (Seq.map ((@) [x]) (getCombs (k - 1) xs)) (getCombs k xs)
/// Gets all combinations (with repetition) of specified length from a list.
let rec getCombsWithRep n lst =
match n, lst with
| 0, _ -> seq [[]]
| _, [] -> seq []
| k, (x :: xs) -> Seq.append (Seq.map ((@) [x]) (getCombsWithRep (k - 1) lst)) (getCombsWithRep k xs)
Does anyone have any suggestions for how these functions (algorithms) can be sped up? I'm particularly interested in how the permutation (with and without repetition) ones can be improved. The business involving rotations of lists doesn't look too efficient to me in retrospect.
Update
Here's my new implementation for the getPerms
function, inspired by Tomas's answer.
Unfortunately, it's not really any fast than the existing one. Suggestions?
let getPerms n lst =
let rec getPermsImpl acc n lst = seq {
match n, lst with
| k, x :: xs ->
if k > 0 then
for r in getRotations lst do
yield! getPermsImpl (List.head r :: acc) (k - 1) (List.tail r)
if k >= 0 then yield! getPermsImpl acc k []
| 0, [] -> yield acc
| _, [] -> ()
}
getPermsImpl List.empty n lst
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果您想编写高效的函数代码,那么最好避免使用
@
运算符,因为列表的串联效率非常低。下面是如何编写一个函数来生成所有组合的示例:
该函数的参数为:
acc
用于记住已选择要包含在组合中的元素(最初是一个空列表)size
是我们需要添加到acc
的元素的剩余数量(最初这是组合所需的大小)set
是可供选择的集合元素该功能是使用简单的递归实现的。如果我们需要生成大小为 n 的组合,那么我们可以添加或不添加当前元素,因此我们尝试使用这两个选项(第一种情况)生成组合并将它们全部添加到使用
yield!
生成序列。如果我们还需要 0 个元素,那么我们成功生成了一个组合(第二种情况),如果我们以其他数字结尾但没有任何剩余元素可供使用,那么我们无法返回任何内容(最后一种情况)。与重复的组合是类似的 - 不同之处在于您不需要从列表中删除元素(通过在递归调用中仅使用
xs
),因此有更多的选择。If you want to write efficient functional code, then it is a good idea to avoid using the
@
operator, because concatentation of lists is very inefficient.Here is an example of how you can write a function to generate all combinations:
The parameters of the function are:
acc
is used to remember elements that are already selected to be included in the combination (initially this is an empty list)size
is the remaining number of elements that we need to add toacc
(initially this is the required size of the combinations)set
is the set elements to choose fromThe function is implemented using a simple recursion. If we need to generate combinations of size
n
then we can either add or don't add the current element, so we try to generate combinations using both options (first case) and add all of them to the generated sequence usingyield!
. If we need 0 more elements, then we successfuly generated a combination (second case) and if we end with some other number but don't have any remaining elements to use then we cannot return anything (last case).Combinations with repetition would be similar - the difference is that you don't need to remove the elements from the list (by using just
xs
in the recursive calls) so there are more options of what to do.我注意到您更新的 getPerms 函数包含重复项。这是我对无欺骗版本的破解。希望这些评论不言而喻。最难的部分是编写一个高效的
distrib
函数,因为必须在某个地方使用串联运算符。幸运的是,它仅用于小型子列表,因此性能仍然合理。下面的 getAllPerms 代码在大约四分之一秒内生成 [1..9] 的所有排列,在大约 2.5 秒内生成所有 10 元素排列。编辑:有趣,我没有看 Tomas 的代码,但他的组合函数和我的选择函数几乎相同。
编辑:响应评论的更多代码
I noticed that your updated getPerms function contains duplicates. Here's my crack at a dupe-free version. Hopefully the comments speak for themselves. The hardest part was writing an efficient
distrib
function, because the concatenation operator has to be used somewhere. Luckily it's only used on small sublists, so the performance remains reasonable. My getAllPerms code below generates all permutations of [1..9] in around a quarter of a second, all 10-element permutations in around 2.5 seconds.Edit: funny, I didn't look at Tomas' code, but his combinations function and my picks function are nearly identical.
Edit: more code in response to comments
如果您确实需要速度,我鼓励您首先为您的问题找到最快的算法,并且如果该算法本质上是命令式的(例如冒泡排序或埃拉托斯特尼筛法),请务必使用 F# 的命令式功能用于您的内部实现,同时保持您的 API 对于库使用者来说是纯粹的(您需要做更多的工作和风险,但对于库使用者来说效果很好)。
针对您的问题,我已经调整了我的快速实现,用于按字典顺序生成一组排列的所有排列(最初提出此处)生成 r 长度排列:
我不确定将此算法适应 r 长度排列是否非常不合适(即是否有更好的命令式或函数式算法)对于这个问题),但平均而言,它的执行速度几乎是
[1;2;3;4;5;6;7; 集合的最新
,并且具有按字典顺序生成 r 长度排列的附加功能(还有趣地注意getPerms
实现的两倍。 8;9]lexPermsAsc
作为 r 的函数如何不是单调的):If you have a real need for speed, I encourage you to first find the fastest algorithm for your problem and if the algorithm turns out to be inherently imperative (e.g. bubble sort or the Sieve of Eratosthenes), by all means, use F#'s imperative features for your implementation internally while keeping your API pure for library consumers (more work and risk for you, but excellent results for library consumers).
Specific to your question, I've adapted my fast implementation for generating all permutations of a set lexicographically (originally presented here) to generate r-length permutations:
I am not sure if adapting this algorithm to r-length permutations is terribly inappropriate (i.e. whether there are better imperative or functional algorithms specifically for this problem), but it does, on average, perform almost twice as fast as your latest
getPerms
implementation for the set[1;2;3;4;5;6;7;8;9]
, and has the additional feature of yielding the r-length permutations lexicographically (notice also with interest howlexPermsAsc
is not monotonic as a function of r):