Plan 9 from Bell Labs’s /usr/web/sources/contrib/fernan/nhc98/tests/nofib/spectral/cryptarithm2/Main.hs

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


{- 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")

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.