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 (876)/(321) = 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"]
λ>
λ> length combis38
56
λ>
So functionally at least, the idea of using multisets to generate combinations works.