I've recently completed (for self study) a homework assignment of some UPenn Haskell Course of building a Mastermind solver.
The assignment starts off with the definitions of
data Color = Red | Green | Blue | Yellow | Orange | Purple deriving (Eq, Show)
type Code = [Color]
and involves various manipulations of enumeration types. Although I've managed to complete the assignment, there were several parts where I felt my code was unnecessarily repetitive or inefficient:
For the purpose of enumerating all codes of length n (i.e., all Cartesian combinations of length n of Color
values), I've used
allColors = [Red, Green, Blue, Yellow, Orange, Purple]
This seems repetitive and brittle, as it's just all the possibilities of Color
. Is there a way to singly define the enumerations, and then build a list from them? Conceptually, I'd like something like allColors = listAll(Color)
(where the question is what is listAll
).
Since many of the expressions contained (if l == r then 1 else 0)
(where l
, r
are Color
s), I've ended up writing a boolToInt
function. Surely there's some way to compare two enumeration types and cast the result to an integer more easily, no? I.e., I'd like Red == Red
to evaluate to 1, and Red == Blue
to evaluate to 0.
Part of the solution requires building a histogram from a Code
, which I did using
countColor :: Color -> Code -> Int
countColor _ [] = 0
countColor c (r:rs) = (boolToInt (c == r)) + countColor c rs
countColors :: Code -> [Int]
countColors code = [countColor c code | c <- allColors]
This seems both inefficient and verbose. Is there any shorter + more efficient way of doing this?
1) If we define
data Color = Red | Green | Blue | Yellow | Orange | Purple
deriving (Eq, Show, Enum, Bounded, Ord)
then we can use
> [minBound .. maxBound] :: [Color]
[Red,Green,Blue,Yellow,Orange,Purple]
2) To convert a boolean into an integer, we can use
> fromEnum True
1
> fromEnum False
0
3) For the histogram, we can start by sorting and grouping:
> import Data.List
> map (\xs -> (head xs, length xs)) . group . sort $ [Red, Red, Blue, Red]
[(Red,3),(Blue,1)]
adding zeros is left as an exercise.
Using an array instead of a list could improve the performance by removing a O(log n) factor, but I'd avoid that unless we know that performance really matters here.