{- Andy Gill, Oct 99 Here is a generic cryptarithm solver, written in Haskell. It does use a State Monad library, which is based on the work documented in "Functional Programming with Overloading and Higher-Order Polymorphism", Mark P. Jones, Advanced School of Functional Programming, 1995. This can solve the puzzle in about 3 seconds on my laptop. On key optimization is captured by the line guard (topN `mod` 10 == botN) in the function solve. It prunes searches than simply can not ever reach valid results. -} module Main where import Monad import MonadState import List import Maybe -- newtype DigitState = DigitState (Digits -> [(a,Digits))]) -- which some might recognize as the list-of-successes parsing monad. type DigitState a = StateT Digits [] a -- Our digits state -- * First we have the remaining digit to allocate. -- * Second, we have the mapping from Char to Digit, -- for the chars that have been mapped so far. data Digits = Digits { digits :: [Int], digitEnv :: [(Char,Int)] } deriving Show initState = Digits { digits = [0..9], digitEnv = [] } -- permute adds a mapping from a char to each of the -- remaining allocable digits. -- This is used in the context of the list-of-successes -- monad, so it actually returns all possible mappings. permute :: Char -> DigitState Int permute c = do st <- get let xs = digits st (i,is) <- lift [ (x,xs \\ [x]) | x <- xs] put (st { digits = is, digitEnv = (c,i):digitEnv st }) return i -- select attempt first checks to see if a mapping -- from a specific char to digit already has been -- mapped. If so, use the mapping, otherwise -- add a new mapping. select :: Char -> DigitState Int select c = do st <- get case lookup c (digitEnv st) of Just r -> return r Nothing -> permute c -- solve takes a list of list of (backwards) letters, -- and a list of (backwards) letters, and tries -- to map the letter to digits, such that -- the sum of the first list of letters (mapped to digits) -- is equal to the sum of the second list of letters, -- again mapped to digits. -- -- So a possible mapping for A+B=C might be -- solve ["A","B"] "C" 0 -- => A -> 1, B -> 2, C -> 3 solve :: [[Char]] -> [Char] -> Int -> DigitState () solve tops (bot:bots) carry = do topN <- (case tops of [] -> return carry (top:_) -> do topNS <- mapM select top return (sum topNS + carry)) botN <- select bot guard (topN `mod` 10 == botN) -- key optimization solve (rest tops) bots (topN `div` 10) where rest [] = [] rest (x:xs) = xs solve [] [] 0 = return () solve _ _ _ = mzero -- Puzzle provides a cleaner interface into solve. -- The strings are in the order *we* write them. puzzle :: [[Char]] -> [Char] -> String puzzle top bot = if length (nub (concat top ++ bot)) > 10 then error "can not map more than 10 chars" else if topVal /= botVal then error ("Internal Error") else unlines [ [c] ++ " => " ++ show i | (c,i) <- digitEnv answer ] where solution = solve (transpose (map reverse top)) (reverse bot) 0 answer = case (execStateT solution initState) of (a:_) -> a [] -> error "can not find a solution" env = digitEnv answer look c = fromJust (lookup c env) topVal = sum [expand xs | xs <- top] botVal = expand bot expand = foldl (\ a b -> a * 10 + look b) 0 main = putStr ( puzzle ["THIRTY", "TWELVE", "TWELVE", "TWELVE", "TWELVE", "TWELVE"] "NINETY")