Repository navigation
Expand file tree
/
Copy pathrepl.clj
More file actions
339 lines (263 loc) · 9.76 KB
/
Copy pathrepl.clj
File metadata and controls
339 lines (263 loc) · 9.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
;;;; Water pouring problem at the REPL
(ns clj-water-pouring.repl
(:require [clojure.pprint :refer [pprint]]))
;;; I start at the REPL by declaring inital-state
(def initial-state
[{:capacity 5
:current 0}
{:capacity 3
:current 0}
])
;;; Then final-state
(def final-state
[{:capacity 5
:current 4}
{:capacity 3
:current 0}
])
;;; I start to implement pour and fill functions
(defn pour
([glass]
(assoc glass :current 0))
([glass quantity]
(let [{:keys [current]} glass]
(assoc glass :current (- current
(min current quantity))))))
;; Test pour
;(pour {:capacity 5 :current 4})
;(pour {:capacity 8 :current 4} 2)
;(pour {:capacity 8 :current 4} 5)
(defn fill
([glass]
(assoc glass :current (:capacity glass)))
([glass quantity]
(let [{:keys [capacity current]} glass]
(assoc glass :current (+ current
(min (- capacity current) quantity))))))
;; Test `fill`
;(fill {:capacity 5 :current 0})
;(fill {:capacity 8 :current 4} 1)
;(fill {:capacity 8 :current 7} 2)
;;; Now I'd need a function that takes a collection of glasses (state),
;;; apply an action (commonly called a 'move' in the current domain language)
;;; and return a new, updated collection of glasses (new state).
;;; I'm beginning to think that a bit of *Ad-hoc polymorphism* would
;;; be great in order to dispatch on the type of 'move': let's use
;;; *multi-methods*.
(defmulti ->move
"Apply a move to the given state and return the new `state`."
(fn [state move] (:type move)))
(defmethod ->move :empty
[state {:keys [from]}]
(update-in state [from] pour))
(defmethod ->move :fill
[state {:keys [to]}]
(update-in state [to] fill))
(defmethod ->move :pour
[state {:keys [from to]}]
(let [quantity (min (get-in state [from :current])
(- (get-in state [to :capacity])
(get-in state [to :current])))]
(-> state
(update-in [from] pour quantity)
(update-in [to] fill quantity))))
;; Test `->move`
;```(-> initial-state
; (->move {:type :fill :to 1})```
; (->move {:type :pour :from 1 :to 0})
; (->move {:type :fill :to 1})
; (->move {:type :pour :from 1 :to 0})
; (->move {:type :empty :from 0})
; (->move {:type :pour :from 1 :to 0})
; (->move {:type :fill :to 1})
; (->move {:type :pour :from 1 :to 0})
; )
;
;;; I must now implement a function that when given a collection of
;;; glasses will return a collection of possible moves that,
;;; when applied to the collection of glasses in input, would return a
;;; meaningful new collection of glasses.
;;; Meaningful as in:
;;; * not emptying an empty glass,
;;; * not filling a glass that is full, and
;;; * not pouring into 'itself', which would lead to the input
(defn glasses->index
"Filter the `glasses` by the `filter-fn` function and
return the index the filtered item had in the
`glasses` collection."
[glasses filter-fn]
(->> glasses
(map-indexed #(vector %1 %2))
(filter (fn [[idx value]] (filter-fn value)))
(map first)))
;; Test `glasses->index`
;(glasses->index final-state (comp pos? :current))
;(glasses->index final-state (comp #(= 3 %) :capacity))
(defn available-moves
"Return the list of valid moves from current state of glasses."
[glasses]
(let [non-empty (glasses->index glasses (comp pos? :current))
non-full (glasses->index glasses #(< (:current %) (:capacity %)))]
(concat
(map #(hash-map :type :empty :from %) non-empty)
(map #(hash-map :type :fill :to %) non-full)
; Oh so beautiful cartesian product
(for [from non-empty to non-full :when (not= from to)]
{:type :pour :from from :to to}))))
;; Test `available-moves`
;(available-moves initial-state)
;(available-moves final-state)
;;; Explore the next glasses available from the current glasses.
(defn explore
[glasses moves]
(map (partial ->move glasses) moves))
;; Test `explore`
;(explore initial-state (available-moves initial-state))
;;; Given a node containing glasses and the sequence of moves leading to
;;; them, return a list of successor nodes.
(defn expand
[{:keys [glasses moves] :as node}]
(let [next-moves (available-moves glasses)]
(->> next-moves
(map #(hash-map :glasses (->move glasses %)
:moves (conj moves %))))))
;; Test `expand`
;(expand
; {:glasses [{:capacity 5 :current 5} {:capacity 3 :current 0}]
; :moves [{:type :fill :to 0}]})
;;; Backtract already visited nodes
(defn backtrack
"Returns true if a node has been visited."
[visited {:keys [glasses]}]
(not (contains? visited glasses)))
;; Test `backtrack`
;(backtrack #{[{:capacity 5 :current 0} {:capacity 3 :current 0}]}
; {:glasses [{:capacity 5 :current 5} {:capacity 3 :current 0}]
; :moves [{:type :fill :to 0}]})
;;; Identify a solution among a set of candidates
(defn has-solution?
[target successors]
(some #(when (= target (:glasses %)) (:moves %))
successors))
;; Test `has-solution?`
;(has-solution?
; [{:capacity 5 :current 4} {:capacity 3 :current 0}]
; [])
;(has-solution?
; [{:capacity 5 :current 4} {:capacity 3 :current 0}]
; [{:glasses [{:capacity 5 :current 0}] :moves []}
; {:glasses [{:capacity 5 :current 4} {:capacity 3 :current 0}] :moves [:foo :bar]}])
;;; Loop over glasses nodes, keeping track of the already visited ones,
;;; searching for a solution leading to the targetted state.
(require '[clojure.set :as set])
(defn solver
[target]
(let [has-solution-fn? (partial has-solution? target)]
(fn [glasses]
(loop [visited #{} nodes [{:glasses glasses :moves []}]]
(if-let [solution (has-solution-fn? nodes)]
solution
(let [successors (mapcat #(expand %) nodes)
valid-successors (filter (partial backtrack visited) successors)]
(recur (clojure.set/union visited (into #{} (map :glasses valid-successors)))
valid-successors)))))))
;; Test `solver`
;(def simple-solver (solver [{:capacity 5 :current 4} {:capacity 3 :current 0}]))
;(simple-solver [{:capacity 5 :current 0} {:capacity 3 :current 0}])
;(def less-simple-solver (solver [{:capacity 8 :current 4} {:capacity 5 :current 0} {:capacity 3 :current 0}]))
;(pprint (less-simple-solver #{} [{:capacity 8 :current 0} {:capacity 5 :current 0} {:capacity 3 :current 0}]))
;;; Refactor
;;; Refactor `->move` fn to better reflect domain
(defmulti ->move
"Apply a move to the given state and return the new state."
(fn [glasses move] (:type move)))
(defmethod ->move :empty
[glasses {:keys [from]}]
(update-in glasses [from] pour))
(defmethod ->move :fill
[glasses {:keys [to]}]
(update-in glasses [to] fill))
(defmethod ->move :pour
[glasses {:keys [from to]}]
(let [quantity (min (get-in glasses [from :current])
(- (get-in glasses [to :capacity])
(get-in glasses [to :current])))]
(-> glasses
(update-in [from] pour quantity)
(update-in [to] fill quantity))))
;;; Refactor `expand` by extracting the node builder
(defn make-node
[glasses moves]
{:glasses glasses
:moves moves})
(defn expand-node
[{:keys [glasses moves]}]
(let [next-moves (available-moves glasses)]
(->> next-moves
(map #(make-node (->move glasses %) (conj moves %)))
;(filter #(not= glasses %))
)))
;;; Refactor `solver` by decomposing functions
(defn make-glass
([capacity]
(make-glass capacity 0))
([capacity current]
{:capacity capacity :current current}))
;; Test `make-glass``
;(make-glass 5)
;(make-glass 8 3)
(defn initialize
([capacities]
(vec (map make-glass capacities)))
([capacities quantities]
(vec (map make-glass capacities quantities))))
;; Test `initialize``
;(initialize [8 5 3])
;(initialize [8 5 3] [4 0 0])
(defn find-successors
[nodes]
(mapcat #(expand-node %) nodes))
;; Test `find-successors`
;(find-successors [{:glasses [{:capacity 5 :current 0} {:capacity 3 :current 0}] :moves []}])
(defn filter-successors
[successors visited]
(filter (partial backtrack visited) successors))
;; Test `filter-successors`
;(filter-successors
; [{:moves [{:type :fill :to 0}] :glasses [{:capacity 5, :current 5} {:capacity 3, :current 0}]}
; {:moves [{:type :fill, :to 1}], :glasses [{:capacity 5, :current 0} {:capacity 3, :current 3}]}]
; #{[{:capacity 5, :current 5} {:capacity 3, :current 0}]})
(defn distinct-glasses
[successors]
(into #{} (map :glasses successors)))
;; Test `distinct-glasses``
;(distinct-glasses
; [{:moves [{:type :fill :to 0}] :glasses [{:capacity 5, :current 5} {:capacity 3, :current 0}]}
; {:moves [{:type :fill, :to 1}], :glasses [{:capacity 5, :current 5} {:capacity 3, :current 0}]}])
(defn solver
[capacities quantities]
(let [initial (initialize capacities)
target (initialize capacities quantities)
has-solution-fn? (partial has-solution? target)]
(fn []
(let [first-node (make-node initial [])]
(loop [visited #{initial} nodes [first-node]]
(if-let [solution (has-solution-fn? nodes)]
solution
(let [successors (find-successors nodes)
valid-successors (filter-successors successors visited)
unique-glasses (distinct-glasses valid-successors)]
(recur (clojure.set/union visited unique-glasses)
valid-successors))))))))
;; Test `solver`
(def simple-solver (solver [5 3] [4 0]))
(pprint (simple-solver))
;(def less-simple-solver (solver [8 5 3] [4 0 0]))
;(pprint (less-simple-solver))
;;; Implement `-main`
(defn -main
[& args]
(assert (even? (count args)))
(let [input (map #(Integer. (re-find #"\d+" %)) args)
solver-fn (apply solver (split-at (/ (count input) 2) input))]
(pprint (solver-fn))))