1 | {-
|
2 |
|
3 | Pearl 6: Making a century
|
4 |
|
5 | Problem:
|
6 | Given the digits 1..9, list all the ways the operations + and x can be inserted into
|
7 | the sequence so as to make the total sum 100.
|
8 | For example:
|
9 | 100 = 12 + 34 + 5x6 +7 + 8 + 9
|
10 | 100 = 1 + 2x3 + 4 + 5 + 67 + 8 + 9
|
11 |
|
12 | No parthneses, normal order of operations.
|
13 |
|
14 | Solution:
|
15 | This is a brute-force problem, but the goal of the pearl is to establish some
|
16 | general formulation of 'Brute-Force' search, and improve on it with 'little'
|
17 | assumptions.
|
18 |
|
19 | Two main things for brute-force methods:
|
20 | 1. Generating the 'values' while generating the 'candidates', can produce savings (Especially
|
21 | if generations of candidates can be 'sequential').
|
22 | 2. Reduce 'good' critertia to 'ok', it can help trim the generation of all sequences.
|
23 |
|
24 | -}
|
25 |
|
26 | -- You CAN skip all the general formulation description and go to ======
|
27 |
|
28 | -- General formulation:
|
29 | -- candidates :: Data -> [Candidate]
|
30 | -- value :: Candidate -> Value
|
31 | -- good :: Value -> Bool
|
32 |
|
33 | -- Brute force search over all candidates
|
34 | -- solutions :: Data -> [Candidate]
|
35 | -- solutions = filter (good.value).candidates
|
36 |
|
37 | -- Now, using a few assumptions we can modify thigns:
|
38 |
|
39 | -- Assumption 1: This will allow us to fuse operations.
|
40 | --
|
41 | -- Data is a list of values, [Datum], and candidates takes the form
|
42 | -- candidates :: [Datum] -> [Candidate]
|
43 | -- candidates = foldr extend []
|
44 | -- where
|
45 | -- extend :: Datum -> [Candidate] -> [Candidate]
|
46 | --
|
47 | --
|
48 | -- Assumption 2: This will allow us to extend search only as needed.
|
49 | --
|
50 | -- 2.a There is a predicate 'ok', such that every good value is necesserialy 'ok'
|
51 | -- 2.b candidates with 'ok' values are the extension of candidates with 'ok' value.
|
52 | --
|
53 | -- Assumption 3: This will save us computing 'value' of candidate
|
54 | --
|
55 | -- map value.extend x = modify x.map value
|
56 | -- The values of an extended set can be computed from the values of which the extension was
|
57 | -- built from.
|
58 | --
|
59 | -- We then introduce a few operations on 'fork', 'cross', and relates these
|
60 | -- to zip and unzip.
|
61 | -- fork (f,g) x = (f x, g x)
|
62 | -- cross (f,g) (x,y) = (f x, g y)
|
63 | --
|
64 | -- and we get:
|
65 |
|
66 | -- solutions :: Data -> [Candidate]
|
67 | -- solutions = map fst.filter (good.snd).foldr expand []
|
68 |
|
69 | -- expand x = filter (ok.snd).zip.cross (extend x, modify x).unzip
|
70 |
|
71 | module P6 where
|
72 |
|
73 | import Data.List (intercalate)
|
74 |
|
75 | -- ================================================
|
76 |
|
77 |
|
78 |
|
79 | -- So all the above was general design-framework.
|
80 | -- now, to the specific problem (creating 100)
|
81 |
|
82 | -- Expression = sum of terms
|
83 | -- Term = products of factors
|
84 | -- Factor = sequence of digits
|
85 |
|
86 | -- Candidate solution are expressions built from + and x
|
87 | -- Remember: No parnthesis etc, and simple prioity.
|
88 | type Expression = [Term] -- Each expression is the sum of Terms
|
89 | type Term = [Factor] -- Each term is the product of factors
|
90 | type Factor = [Digit] -- Each factor is list of digits
|
91 | type Digit = Int
|
92 |
|
93 |
|
94 | -- Just computing the value:
|
95 | valExpr :: Expression -> Int
|
96 | valExpr = sum.map valTerm
|
97 | valTerm = product.map valFact
|
98 | valFact = foldl (\n d -> 10*n+d) 0
|
99 |
|
100 |
|
101 | good :: Int -> Bool
|
102 | good v = (v==100)
|
103 |
|
104 | ok :: Int -> Bool
|
105 | ok v = (v<=100)
|
106 |
|
107 | -- Creating all possible expressions
|
108 | -- They mention there is a function 'partitions', but never spell it out.
|
109 | -- so here it is, even though we will NOT use it!
|
110 | partitions :: [Digit] -> [[[Digit]]]
|
111 | partitions [] = [[]]
|
112 | partitions (x:xs) = [[x]:p | p <- partitions xs] -- x is a prt of it's own
|
113 | ++ [(x:ys):yss | (ys:yss) <- partitions xs] -- x is with the next ones
|
114 |
|
115 | -- A difffernet way
|
116 | expressions xs = foldr extend [] xs
|
117 |
|
118 | extend :: Digit -> [Expression] -> [Expression]
|
119 | extend x [] = [[[[x]]]]
|
120 | extend x es = concatMap (glue x) es
|
121 |
|
122 | -- glue: Each new digit can be added in one of 3-different ways to the expression:
|
123 | -- as a digit; as factor (multiplication); or as a term ==> 3 options.
|
124 | -- BTW, this shows the number of possible expressions is 3^(n-1)
|
125 | glue :: Digit -> Expression -> [Expression]
|
126 | glue x ((xs:xss):xsss) = [((x:xs):xss):xsss, -- digit
|
127 | ([x]:xs:xss):xsss, -- factor
|
128 | [[x]]:(xs:xss):xsss] -- term
|
129 |
|
130 |
|
131 | -- Main
|
132 |
|
133 | main = do
|
134 | -- putStr "Testing partitions: "
|
135 | -- print $ partitions [1..4]
|
136 |
|
137 | putStrLn "Brute force : "
|
138 | let res = filter (good.valExpr) $ expressions [1..9]
|
139 | --print res
|
140 | prettyPrint res
|
141 |
|
142 |
|
143 | -- pretty
|
144 | prettyPrint es = putStr $ unlines $ map printExpression es
|
145 |
|
146 | printExpression :: Expression -> String
|
147 | printExpression e = "100 = " ++
|
148 | ( intercalate " + " $ map printTerm e)
|
149 |
|
150 | printTerm t = intercalate "*" $ map printFactor t
|
151 | printFactor f = concat $ map show f
|