Return list with numbers of color occurrences in another list

Posted on

Problem

I’m solving “mastermind game” problem from CIS haskell course. Here is the link to assignment pdf if someone is interested.

List of possible colors is defined like this:

data Peg = Red | Green | Blue | Yellow | Orange | Purple
     deriving (Show, Eq, Ord)
type Code = [Peg]
colors :: [Peg]
colors = [Red, Green, Blue, Yellow, Orange, Purple]

And I had to write a function that will take a list with color guesses and return the occurrence count for each available color in the form of a list.

countColors :: Code -> [Int]
countColors a        = map ((x,y) -> length $ filter (x==) y) combo
      where combo    = zip colors (take 6 $ repeat a)

It works fine but I’d like to know if there is better/more expressive way to write it (using most basic available functions).

I’m not sure what’s good practice in Haskell; that is my main problem. I have tried to write in point free notation at first but it looked overly complicated.

My worry is that code is not clear enough. I like to have code that is as obvious as possible.

Here are test cases in case it helps:

countColors [Red, Blue, Yellow, Purple] == [1, 0, 1, 1, 0, 1]
countColors [Green, Blue, Green, Orange] == [0, 2, 1, 0, 1, 0]

Solution

You could map each color to the number of times it occurs in the given list, i.e.:

countColors :: [Peg] -> [Int]
countColors xs = map (c -> length (filter (== c) xs)) colors

I think you’re tripping yourself up by position-encoding the colors. Position-encoding is usually a code smell in Haskell, what you probably want is an association. Consider the difference between [Int] and [(Peg, Int)], and just how much more expressive of intent the latter is.

(You could be even more specific at the type level with something like type Count = Int countColors :: Code -> Map Peg Count but that’s probably excessive for a small game.)

So then how do you write a function with type Code -> [(Peg, Int)]? As you noticed the [Peg] may be in any order, so to compensate you can filter for each individual color, or you could leverage the Ord Peg instance and sort the original list to group identical colors together. After the colors are in order, what’s left is to simply run-length encode the list.

countColors :: Code -> [(Peg, Int)]
countColors = map (shade -> (head shade, length shade)) . group . sort

Leave a Reply

Your email address will not be published. Required fields are marked *