For the Who Owns the Zebra problem I initially tried a solution based on a list comprehension. I was, however, unable to get it to work in any reasonable time. Today Rofl_Waffler posted a solution on reddit that showed me why: I put all the conditions at the end, which meant that all possible solutions had to be generated. By interleaving the options and the conditions, Haskell does do the smart filtering you would expect. Although it does require careful ordering of the code, the resulting solution is a lot shorter. Rofl_Waffler’s solution uses a do statement with guards, but I figured I could make it even better using a list comprehension and some other minor adjustments.
In this approach all permutations of the five properties are generated. To figure out the position of a specific option we use the following function:
indexOf :: (Eq a) => [a] -> a -> Int indexOf xs x = head $ elemIndices x xs
We also need to be able to tell if an option is next to or to the right of another option:
nextTo :: Int -> Int -> Bool nextTo a b = abs (a - b) == 1 rightOf :: Int -> Int -> Bool rightOf a b = a == b + 1
A small convenience function to generate the different permutations:
options :: String -> [[String]] options = permutations . words
And the solution to the problem itself.
solution :: [[String]] solution = head [transpose [cs, os, ds, ss, ps] | cs <- options "red green ivory yellow blue", let color = indexOf cs, color "green" `rightOf` color "ivory", os <- options "english spaniard ukranian norwegian japanese", let owner = indexOf os, owner "norwegian" == 0, owner "english" == color "red", owner "norwegian" `nextTo` color "blue", ds <- options "coffee tea milk juice water", let drinks = indexOf ds, drinks "milk" == 2, drinks "coffee" == color "green", owner "ukranian" == drinks "tea", ss <- options "old_gold kools chesterfields parliaments lucky_strike", let smokes = indexOf ss, smokes "kools" == color "yellow", smokes "lucky_strike" == drinks "juice", owner "japanese" == smokes "parliaments", ps <- options "dog snails fox horse zebra", let pet = indexOf ps, owner "spaniard" == pet "dog", smokes "old_gold" == pet "snails", smokes "chesterfields" `nextTo` pet "fox", smokes "kools" `nextTo` pet "horse"]
A quick test shows that we still get the correct answer.
main :: IO () main = mapM_ print solution
Now we only need 30 lines, which is a reduction of just under 50%. The lesson here: put conditions in list comprehensions as close to the generators as possible.