Countdown 2

In my previous post, I bodged a function to aggregate additions and multiplications into a program based on individual binary operations.

This is what it looks like if it is designed to use Sum’ (the prime disambiguates it from the built-in monoid) and Prod from the beginning.

It is only a little shorter, and much harder to read. I like to think this reflects my bodging skills rather than my refactoring skills.

It is annoying that the definitions of (.+) etc are similar enough to generate with copy/paste and search/replace, but I cannot reduce them.

import System.Environment
import Data.List (partition,nub,sortBy)
import Data.Maybe (isJust)
import qualified Data.IntMap.Strict as M
import Control.Monad (filterM)

data Expr = Single Int | 
            Sum' [Expr] (Maybe Int) | 
            Prod [Expr] (Maybe Int) | 
            Diff Expr Expr (Maybe Int) | 
            Ratio Expr Expr (Maybe Int)

-- sort the arguments of Sum' or Prod - the ordering only needs to be consistent
srt::[Expr]->[Expr]
srt xs =  sortBy (\x y -> compare (show (eval x)) (show (eval y))) xs

instance Show Expr where
  show (Single i) = concat [(show i)," "]
  show (Sum' xs val) = concat ["(Sum [",(concat (map show (srt xs))),"])"]
  show (Prod xs val) = concat ["(Prod [",(concat (map show (srt xs))),"])"]
  show (Diff x y val) = concat ["(",(show x), "-", (show y),")"]
  show (Ratio x y val) = concat ["(",(show x), "/", (show y),")"]

-- val is maintained by the combinators
eval::Expr->Maybe Int
eval (Single i) = Just i
eval (Sum' _ val) = val
eval (Prod _ val) = val
eval (Diff _ _ val) = val
eval (Ratio _ _ val) = val

(~+)::Maybe Int->Maybe Int->Maybe Int
(~+) x y = ((pure (+))<*>x<*>y)

(~-)::Maybe Int->Maybe Int->Maybe Int
(~-) x y = (pure (-))<*>x<*>y

(~*)::Maybe Int->Maybe Int->Maybe Int
(~*) x y = (pure (*))<*>x<*>y

-- i>=j enforced at call site (combine)
(~/)::Maybe Int->Maybe Int->Maybe Int
(~/) (Just i) (Just j) | ((j>1) && ((mod i j)==0)) = Just (div i j)
(~/) _ _ = Nothing

-- cases: Sum' Sum', Sum' Diff, Diff Sum', Diff Diff, other Sum', Sum' other, other other
(.+)::Expr->Expr->Expr
(.+) (Sum' xs valx)   (Sum' ys valy)   = Sum' (xs ++ ys) ((~+) valx valy)
(.+) (Sum' xs valx)   (Diff y y' valy) = Diff ((.+) (Sum' xs valx) y)  ((.+) y' (Sum' [] (Just 0))) ((~+) valx valy)   
(.+) (Diff y y' valy) (Sum' xs valx)   = (.+) (Sum' xs valx)  (Diff y y' valy) -- commutative
(.+) (Diff x x' valx) (Diff y y' valy) = Diff ((.+) (Sum' [x] (eval x)) (Sum' [y'] (eval y')))  ((.+) (Sum' [x'] (eval x')) (Sum' [y] (eval y))) ((~+) valx valy)
(.+) (Sum' xs valx) y = Sum' (y:xs) ((~+) valx (eval y))
(.+) y (Sum' xs valx) = (.+) (Sum' xs valx) y -- commutative
(.+) x y = Sum' [x,y] ((~+) (eval x) (eval y))

-- cases: Prod Prod, Prod Ratio, Ratio Prod, Ratio Ratio, other Prod, Prod other, other other
(.*)::Expr->Expr->Expr
(.*) (Prod xs valx)    (Prod ys valy)    = Prod (xs ++ ys) ((~*) valx valy)
(.*) (Prod xs valx)    (Diff y y' valy)  = Diff ((.*) (Prod xs valx) y)  ((.*) y' (Prod [] (Just 0))) ((~*) valx valy)   
(.*) (Ratio y y' valy) (Prod xs valx)    = (.*) (Prod xs valx)  (Ratio y y' valy) -- commutative
(.*) (Ratio x x' valx) (Ratio y y' valy) = Ratio ((.*) (Prod [x] (eval x))  (Prod [y'] (eval y')))   ((.*) (Prod [x'] (eval x')) (Prod [y] (eval y))) ((~*) valx valy)
(.*) (Prod xs valx) y = Prod (y:xs) ((~*) valx (eval y))
(.*) y (Prod xs valx) = (.*) (Prod xs valx) y -- commutative
(.*) x y = Prod [x,y] ((~*) (eval x) (eval y))

-- cases: Diff Diff, Diff Sum', Sum' Diff, other Diff, Diff other, other other
(.-)::Expr->Expr->Expr
(.-) (Diff x x' valx) (Diff y y' valy) = Diff ((.+) x y') ((.+) x' y) ((~-) valx valy)
(.-) (Diff y y' valy) (Sum' xs valx)   = Diff y ((.+) y' (Sum' xs valx)) ((~-) valy valx) 
(.-) (Sum' xs valx)   (Diff y y' valy) = Diff ((.+) y (Sum' xs valx)) y' ((~-) valx valy) 
(.-) (Diff x x' valx) y = Diff x ((.+) y x') ((~-) valx (eval y)) 
(.-) x (Diff y y' valy) = Diff ((.+) x y') y ((~-) (eval x) valy) 
(.-) x y = Diff x y ((~-) (eval x) (eval y))

-- cases: Ratio Ratio, Ratio Sum', Sum' Ratio, other Ratio, Ratio other, other other
(./)::Expr->Expr->Expr 
(./) (Ratio x x' valx) (Ratio y y' valy) = Ratio ((.*) x y') ((.*) x' y) ((~/) valx valy)
(./) (Ratio y y' valy) (Prod xs valx)   = Ratio y ((.*) y' (Prod xs valx)) ((~/) valy valx) 
(./) (Prod xs valx)   (Ratio y y' valy) = Ratio ((.*) y (Prod xs valx)) y' ((~/) valx valy) 
(./) (Ratio x x' valx) y = Ratio x ((.*) y x') ((~/) valx (eval y)) 
(./) x (Ratio y y' valy) = Ratio ((.*) x y') y ((~/) (eval x) valy) 
(./) x y = Ratio x y ((~/) (eval x) (eval y))

combine::Expr->Expr->[Expr]
combine  x y = 
   let x' = eval x
       y'= eval y in 
  filter (isJust.eval) $ 
  case (compare x' y') of
    GT -> [(.+),(.-),(.*),(./)]<*>[x]<*>[y]
    EQ -> [(.+),(.*),(./)]<*>[x]<*>[y] -- no subtraction because 0 would be redundant
    LT -> [(.+),(.-),(.*),(./)]<*>[y]<*>[x]
    
check::Int->Expr->Bool
check target exp = case (eval exp) of
  Just i -> i == target
  _      -> False 

-- combinatorial functions
-- non-trivial masks: each generated partition must be non-empty to ensure recursion progresses
-- 2 and 1 are special cases at call site in expressions
-- don't need reciprocal masks: TTF will generate the same partition as FFT
masks::Int->[[Bool]]
masks 1 = [[True]]  
masks 2 = [[True,True],[False,True]]
masks n = filter (any (==False)) [x : y | x<-[True,False], y<-masks (n-1)]

-- memoised; hardcoded max length 6
masksM::Int->[[Bool]]
masksM i = let m = M.fromAscList $ map (\x->(x,masks x)) [1..6]
  in (M.!) m i

partitions::[a]->[([a],[a])]
partitions xs = 
  map (\(x,y)->((map fst x,map fst y))) $ 
  map (partition (\x->(snd x)==True)) $ 
  map (zip xs) $  
  masksM (length xs) 

powerset::[a]->[[a]]
powerset xs = filterM (\x->[True,False]) xs 

-- all the expressions that use all the numbers in the partition
expressions::[Expr]->[Expr]
expressions [] = []
expressions (a:[]) = [a]
expressions (a:b:[]) = a:b:combine a b
expressions xs =  
  concat $ 
  map (\(a,b) -> concat [combine x y | x<-a,y<-b]) $ -- all the expressions combining the partitions
  map (\(a,b)->(expressions a,expressions b)) $       -- all the expressions from each partition 
  partitions xs                                       -- nontrivial ways to split the list 

solutions'::Int->[Int]->[Expr]
solutions' target xs =
  filter (check target) $ 
  concat $ 
  map expressions $ 
  powerset $ 
  map Single xs

-- deduplicate
solutions::Int->[Int]->[String]
solutions target ns = nub $ map show $ solutions' target ns

main::IO ()
main = do
  args<-getArgs
  let ns = read $ args!!0
  let target = read $ args!!1
  mapM_ putStrLn $ solutions target ns

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: