问题
Is there a Haskell function that generates all the unique combinations of a given length from a list?
Source = [1,2,3]
uniqueCombos 2 Source = [[1,2],[1,3],[2,3]]
I tried looking in Hoogle but could not find a function that did this specifically. Permutations does not give the desired result.
Has anybody used a similar function before?
回答1:
I don't know a predefined function either, but it's pretty easy to write yourself:
-- Every set contains a unique empty subset.
subsets 0 _ = [[]]
-- Empty sets don't have any (non-empty) subsets.
subsets _ [] = []
-- Otherwise we're dealing with non-empty subsets of a non-empty set.
-- If the first element of the set is x, we can get subsets of size n by either:
-- - getting subsets of size n-1 of the remaining set xs and adding x to each of them
-- (those are all subsets containing x), or
-- - getting subsets of size n of the remaining set xs
-- (those are all subsets not containing x)
subsets n (x : xs) = map (x :) (subsets (n - 1) xs) ++ subsets n xs
回答2:
Using Data.List
:
import Data.List
combinations k ns = filter ((k==).length) $ subsequences ns
Reference: 99 Haskell Problems
There are quite a few interesting solutions in the reference, I just picked a concise one.
回答3:
There is no such operation in lib, but you can easily implement it yourself:
import Data.List
main = putStrLn $ show $ myOp 2 [1, 2, 3]
myOp :: Int -> [a] -> [[a]]
myOp 0 _ = []
myOp 1 l = map (:[]) l
myOp c l = concat $ map f $ tails l
where
f :: [a] -> [[a]]
f [] = []
f (x:xs) = map (x:) $ myOp (c - 1) xs
回答4:
It is not clear to me how concerned you would be about performance.
If it can be of any use, back in 2014, somebody posted some sort of performance contest of various Haskell combinations generating algorithms.
For combinations of 13 items out of 26, execution times varied from 3 to 167 seconds ! The fastest entry was provided by Bergi. Here is the non-obvious (for me at least) source code:
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 ++ [[]] )
More recently, the question has been revisited, in the specific context of picking a few elements from a large list (5 out of 100). In that case, you cannot use something like subsequences [1 .. 100]
because that refers to a list whose length is 2100 ≃ 1.26*1030. I submitted a state machine based algorithm which is not as Haskell-idiomatic as I would like, but is reasonably efficient for that sort of situations, something around 30 clock cycles per output item.
Side note: Using multisets to generate combinations ?
Also, there is a Math.Combinatorics.Multiset package available. Here is the documentation. I have only briefly tested it, but it can be used to generate combinations.
For example, the set of all combinations of 3 elements out of 8 are just like the "permutations" of a multiset with two elements (Present and Absent) with respective multiplicities of 3 and (8-3)=5.
Let us use the idea to generate all combinations of 3 elements out of 8. There are (8*7*6)/(3*2*1) = 336/6 = 56 of them.
*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"]
λ>
56
λ>
So functionally at least, the idea of using multisets to generate combinations works.
来源:https://stackoverflow.com/questions/52602474/function-to-generate-the-unique-combinations-of-a-list-in-haskell