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.
- data BinOp
- data UnOp
- type SBinOp = SBV BinOp
- type SUnOp = SBV UnOp
- data T b u
- allPossibleTrees :: [T () ()]
- fill :: T () () -> Symbolic (T SBinOp SUnOp)
- sCase :: (SymWord 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.
Supported unary operators. Similar to BinOp
case, we will restrict square-root and factorial to
be only applied to the value @4.
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 :: (SymWord 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 evalutes 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. We have:
>>>
puzzle
0: (4 + (4 - (4 + 4))) 1: (4 / (4 + (4 - 4))) 2: sqrt((4 + (4 * (4 - 4)))) 3: (4 - (4 ^ (4 - 4))) 4: (4 * (4 ^ (4 - 4))) 5: (4 + (4 ^ (4 - 4))) 6: (4 + sqrt((4 * (4 / 4)))) 7: (4 + (4 - (4 / 4))) 8: (4 - (4 - (4 + 4))) 9: (4 + (4 + (4 / 4))) 10: (4 + (4 + (4 - sqrt(4)))) 11: (4 + ((4 + 4!) / 4)) 12: (4 * (4 - (4 / 4))) 13: (4! + ((sqrt(4) - 4!) / sqrt(4))) 14: (4 + (4 + (4 + sqrt(4)))) 15: (4 + ((4! - sqrt(4)) / sqrt(4))) 16: (4 + (4 + (4 + 4))) 17: (4 + ((sqrt(4) + 4!) / sqrt(4))) 18: -(4 + (4 - (4! + sqrt(4)))) 19: -(4 - (4! - (4 / 4))) 20: (4 * (4 + (4 / 4)))