Search code examples
haskellenumeration

Working With Enumeration Types: Listing, Comparing, and Building Histograms


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:

  1. 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).

  2. Since many of the expressions contained (if l == r then 1 else 0) (where l, r are Colors), 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.

  3. 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?


Solution

  • 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.