我不清楚您对性能的关注程度。
如果它有任何用处,早在 2014 年,有人发布了某种 performance contest 的各种 Haskell 组合生成算法。
对于 26 项中的 13 项的组合,执行时间从 3 秒到 167 秒不等! Bergi 提供了最快的条目。这是不明显的(至少对我而言)源代码:
subsequencesOfSize :: Int -> [a] -> [[a]]
subsequencesOfSize n xs = let l = length xs
in if (n > l) then []
else subsequencesBySize xs !! (l-n)
where
subsequencesBySize [] = [[[]]]
subsequencesBySize (x:xs) = let next = subsequencesBySize xs
in zipWith (++)
([]:next)
( map (map (x:)) next ++ [[]] )
最近,问题是revisited,在从一个大列表(100 个中的 5 个)中挑选几个元素的特定上下文中。在这种情况下,您不能使用 subsequences [1 .. 100] 之类的东西,因为它指的是长度为 2100 ≃ 1.26*1030 的列表。我提交了一个基于 algorithm 的状态机,它不像我希望的那样使用 Haskell 惯用语,但在这种情况下相当有效,每个输出项大约 30 个时钟周期。
旁注:使用 multisets 生成组合?
此外,还有一个Math.Combinatorics.Multiset 包可用。这是documentation。我只是简单地测试了它,但它可以用来生成组合。
例如,8 个元素中的 3 个元素的所有组合的集合就像 multiset 的“排列”,其中两个元素(存在和不存在)分别为 3 和 (8-3)=5 .
让我们用这个想法来生成 8 个元素中的 3 个元素的所有组合。有 (876)/(321) = 336/6 = 56 个。
*L M Mb T MS> import qualified Math.Combinatorics.Multiset as MS
*Math.Combinatorics.Multiset L M Mb T MS> pms = MS.permutations
*Math.Combinatorics.Multiset L M Mb T MS> :set prompt "λ> "
λ>
λ> pms38 = pms $ MS.fromCounts [(True, 3), (False,5)]
λ>
λ> length pms38
56
λ>
λ> take 3 pms38
[[True,True,True,False,False,False,False,False],[True,True,False,False,False,False,False,True],[True,True,False,False,False,False,True,False]]
λ>
λ> str = "ABCDEFGH"
λ> combis38 = L.map fn pms38 where fn mask = L.map fst $ L.filter snd (zip str mask)
λ>
λ> sort combis38
["ABC","ABD","ABE","ABF","ABG","ABH","ACD","ACE","ACF","ACG","ACH","ADE","ADF","ADG","ADH","AEF","AEG","AEH","AFG","AFH","AGH","BCD","BCE","BCF","BCG","BCH","BDE","BDF","BDG","BDH","BEF","BEG","BEH","BFG","BFH","BGH","CDE","CDF","CDG","CDH","CEF","CEG","CEH","CFG","CFH","CGH","DEF","DEG","DEH","DFG","DFH","DGH","EFG","EFH","EGH","FGH"]
λ>
λ> length combis38
56
λ>
至少在功能上,使用多重集合生成组合的想法是可行的。