Copyright | (c) Levent Erkok |
---|---|
License | BSD3 |
Maintainer | erkokl@gmail.com |
Stability | experimental |
Safe Haskell | None |
Language | Haskell2010 |
A query based solution to the four-fours puzzle. Inspired by http://www.gigamonkeys.com/trees/
Try to make every number between 0 and 20 using only four 4s and any mathematical operation, with all four 4s being used each time.
We pretty much follow the structure of http://www.gigamonkeys.com/trees/, with the exception that we generate the trees filled with symbolic operators and ask the SMT solver to find the appropriate fillings.
Synopsis
- data BinOp
- type SBinOp = SBV BinOp
- sExpt :: SBV BinOp
- sDivide :: SBV BinOp
- sTimes :: SBV BinOp
- sMinus :: SBV BinOp
- sPlus :: SBV BinOp
- data UnOp
- type SUnOp = SBV UnOp
- sFactorial :: SBV UnOp
- sSqrt :: SBV UnOp
- sNegate :: SBV UnOp
- data T b u
- allPossibleTrees :: [T () ()]
- fill :: T () () -> Symbolic (T SBinOp SUnOp)
- sCase :: (Eq a, SymVal a, Mergeable v) => SBV a -> [(a, v)] -> v
- eval :: T SBinOp SUnOp -> Symbolic SInteger
- generate :: Integer -> T () () -> IO (Maybe (T BinOp UnOp))
- find :: Integer -> IO ()
- puzzle :: IO ()
Documentation
Supported binary operators. To keep the search-space small, we will only allow division by 2
or 4
,
and exponentiation will only be to the power 0
. This does restrict the search space, but is sufficient to
solve all the instances.
Instances
Supported unary operators. Similar to BinOp
case, we will restrict square-root and factorial to
be only applied to the value @4.
Instances
sFactorial :: SBV UnOp Source #
allPossibleTrees :: [T () ()] Source #
Construct all possible tree shapes. The argument here follows the logic in http://www.gigamonkeys.com/trees/: We simply construct all possible shapes and extend with the operators. The number of such trees is:
>>>
length allPossibleTrees
640
Note that this is a lot smaller than what is generated by http://www.gigamonkeys.com/trees/. (There, the number of trees is 10240000: 16000 times more than what we have to consider!)
fill :: T () () -> Symbolic (T SBinOp SUnOp) Source #
Given a tree with hols, fill it with symbolic operators. This is the trick that allows us to consider only 640 trees as opposed to over 10 million.
sCase :: (Eq a, SymVal a, Mergeable v) => SBV a -> [(a, v)] -> v Source #
Minor helper for writing "symbolic" case statements. Simply walks down a list of values to match against a symbolic version of the key.
eval :: T SBinOp SUnOp -> Symbolic SInteger Source #
Evaluate a symbolic tree, obtaining a symbolic value. Note how we structure this evaluation so we impose extra constraints on what values square-root, divide etc. can take. This is the power of the symbolic approach: We can put arbitrary symbolic constraints as we evaluate the tree.
generate :: Integer -> T () () -> IO (Maybe (T BinOp UnOp)) Source #
In the query mode, find a filling of a given tree shape t, such that it evaluates to the requested number i. Note that we return back a concrete tree.
find :: Integer -> IO () Source #
Given an integer, walk through all possible tree shapes (at most 640 of them), and find a filling that solves the puzzle.
Solution to the puzzle. When you run this puzzle, the solver can produce different results than what's shown here, but the expressions should still be all valid!
ghci> puzzle 0 [OK]: (4 - (4 + (4 - 4))) 1 [OK]: (4 / (4 + (4 - 4))) 2 [OK]: sqrt((4 + (4 * (4 - 4)))) 3 [OK]: (4 - (4 ^ (4 - 4))) 4 [OK]: (4 + (4 * (4 - 4))) 5 [OK]: (4 + (4 ^ (4 - 4))) 6 [OK]: (4 + sqrt((4 * (4 / 4)))) 7 [OK]: (4 + (4 - (4 / 4))) 8 [OK]: (4 - (4 - (4 + 4))) 9 [OK]: (4 + (4 + (4 / 4))) 10 [OK]: (4 + (4 + (4 - sqrt(4)))) 11 [OK]: (4 + ((4 + 4!) / 4)) 12 [OK]: (4 * (4 - (4 / 4))) 13 [OK]: (4! + ((sqrt(4) - 4!) / sqrt(4))) 14 [OK]: (4 + (4 + (4 + sqrt(4)))) 15 [OK]: (4 + ((4! - sqrt(4)) / sqrt(4))) 16 [OK]: (4 * (4 * (4 / 4))) 17 [OK]: (4 + ((sqrt(4) + 4!) / sqrt(4))) 18 [OK]: -(4 + (4 - (sqrt(4) + 4!))) 19 [OK]: -(4 - (4! - (4 / 4))) 20 [OK]: (4 * (4 + (4 / 4)))