0

I'm writing a combs function in haskell what it needs to do is, when I provide it with a deck of cards, give me every combination of hands possible from that deck of size x

This is the relevant code

combs :: Int -> [a] -> [[a]]
combs 0 _      = [[ ]]
combs i (x:xs) =  (filter (isLength i) y)
            where y = subs (x:xs)
combs _ _      = [ ]

isLength :: Int -> [a] -> Bool
isLength i x
        | length x == i = True
        | otherwise     = False

subs :: [a] -> [[a]]
subs [ ] = [[ ]]
subs (x : xs) = map (x:) ys ++ ys
            where ys = subs xs

However, when I ask it to compute a combs 5 [1..52], e.g. a hand of 5 out of a full deck, it does not provide a result, and keeps running for a really long time
Does anyone know what the problem is and how to speed up this algorithm?

LordofTurtles
  • 65
  • 1
  • 8

2 Answers2

3

Right now it's a bit hard to see what you are trying to do - but I guess the problems you have is that you gonna filter and map a lot.

I think a simple way to get what you need is this:

module Combinations where

import Data.List (delete)

combs :: Eq a => Int -> [a] -> [[a]]
combs 0 _ = [[]]
combs i xs = [ y:ys | y <- xs, ys <- combs (i-1) (delete y xs) ]

which uses delete from Data.List

It should be lazy enough to find you combinations quick - of course all will take a while ;)

λ> take 5 $ combs 5 [1..52]
[[1,2,3,4,5],[1,2,3,4,6],[1,2,3,4,7],[1,2,3,4,8],[1,2,3,4,9]]

how does it work

it's one of those recursive combinatorial algorithm that works by selecting a first card y from all the cards xs, and then recursivley gets the rest of the handysfrom the deck without the selected carddelete a xsand then putting it back togethery:ys` inside the list-monad (here using list-comprehensions).

BTW: ther are 311,875,200 such decks ;)

version without list-comprehensions

here is a version without comprehensions in case your system has issues here:

combs :: Eq a => Int -> [a] -> [[a]]
combs 0 _ = [[]]
combs i xs = do
  y <- xs
  ys <- combs (i-1) (delete y xs)
  return $ y:ys

version that will remove permutations

this one uses Ord to get sort the items in ascending order and in doing so removing duplciates in respect to permutaion - for this to work xs is expected to be pre-sorted!

Note chi's version is working with fewer constraints and might be more preformant too - but I thougt this is nice and readable and goes well with the version before so maybe it's of interest to you.

I know it's not a thing often done in Haskell/FP where you strife for the most general and abstract cases but I come form an environment where most strive for readability and understanding (coding for the programmer not only for the compiler) - so be gentle ;)

combs' :: Ord a => Int -> [a] -> [[a]]
combs' 0 _  = [[]]
combs' i xs = [ y:ys | y <- xs, ys <- combs' (i-1) (filter (> y) xs) ]
Random Dev
  • 51,810
  • 9
  • 92
  • 119
  • Two questions: Since "as" is a reserved keyword, should it not be y:ys? and when I copy your code to try it out, I get a parse error on input | – LordofTurtles Oct 12 '14 at 17:48
  • 1
    @LordofTurtles I added a module definition as well - do you get errors if you copy this to some .hs file and load it in ghci still? – Random Dev Oct 12 '14 at 17:53
  • 1
    @LordofTurtles - btw: where do you put it? GHCi? - or is this some other Haskell system? – Random Dev Oct 12 '14 at 17:54
  • It now creates a parse error in input module, oddly enough I am using GHCi, yes Perhaps related, above where I'm putting combs is this statement: instance Ord Hand where x `compare` y = (handCategory x) `compare` (handCategory y) – LordofTurtles Oct 12 '14 at 17:59
  • 1
    @LordofTurtles sorry to say but I think your installation might be glitched - can you put your code on a GIST or add it to your question so I can have a look? Maybe you have some tabs in there (?) – Random Dev Oct 12 '14 at 18:01
  • I apologize, I made an error and forgot to comment out a section of my combs code, leading to the parse error Your code works fine, except that it provides too much output, combs 4 [1,2,3,4] should only give [[1,2,3,4]] as output, but it also includes the other 3 permutations of that set, any way to filter those out easily? – LordofTurtles Oct 12 '14 at 18:17
  • 1
    you mean the permutations of [1,2,3,4]? well yes - I add it - but it will basically be the version chi gave you – Random Dev Oct 12 '14 at 18:36
3

To extract i items from x:xs you can proceed in two ways:

  • you keep the x, and extract only i-1 elements from xs
  • you discard x, and extract all the i elements from xs

Hence, a solution is:

comb :: Int -> [a] -> [[a]]
comb 0 _      = [[]]  -- only the empty list has 0 elements
comb _ []     = []    -- can not extract > 0 elements from []
comb i (x:xs) = [ x:ys | ys <- comb (i-1) xs ]  -- keep x case
                ++ comb i xs                    -- discard x case

By the way, the above code also "proves" a well-known recursive formula for the binomial coefficients. You might already have met this formula if you attended a calculus class. Letting B(k,n) = length (comb k [1..n]), we have

B(k+1,n+1) == B(k,n) + B(k+1,n)

which is just a direct consequence of the last line of the code above.

chi
  • 111,837
  • 3
  • 133
  • 218
  • The same problem occurs as in Carsten's solution, as is a reserved keyword, and I get a parse error on input | Also, I am not trying to extract i elements from x:xs, I am trying to generate all the possible combinations C(i,x:xs), e.g. "how many different hands of 5 can I pull out of a deck of 52 cards" and then generate every single possible hand – LordofTurtles Oct 12 '14 at 17:53
  • 1
    @LordofTurtles Wouldn't `comb 5 allCards` solve that? It should extract all the possible hands, ignoring order. – chi Oct 12 '14 at 18:00
  • Oh wait, your code does exactly what I had in mind, great! I am not particularly good at list comprehensions at this point, so I was confused – LordofTurtles Oct 12 '14 at 18:33