9
9
10
10
for latest versions of my solutions, see my checkio solution github repo:
11
11
https://github.com/jmegner/CheckioPuzzles
12
+
13
+ note: this file contains three alternative solutions
14
+ 1: efficient iterative breadth first search
15
+ 2: inefficient recursive depth first search
16
+ 3: fun iterative algo that does not necessarily find minimal steps
12
17
'''
13
18
14
19
15
- class Jar :
20
+ import collections
21
+ import itertools
22
+
23
+
24
+ ################################################################################
25
+ # efficient iterative breadth first search approach
26
+
27
+ class Jar (collections .namedtuple ("Jar" , ["name" , "volume" , "water" ])):
28
+
29
+ def isEmpty (self ): return self .water == 0
30
+ def isFull (self ): return self .water == self .volume
31
+ def asEmpty (self ): return Jar (self .name , self .volume , 0 )
32
+ def asFull (self ): return Jar (self .name , self .volume , self .volume )
33
+ def fillStr (self ): return "0" + self .name
34
+ def drainStr (self ): return self .name + "0"
35
+ def transferStr (self , other ): return self .name + other .name
36
+
37
+
38
+ def withWaterChange (self , waterChange ):
39
+ return Jar (self .name , self .volume , self .water + waterChange )
40
+
41
+
42
+ class State (collections .namedtuple ("State" , ["jar1" , "jar2" , "steps" ])):
43
+
44
+ def meetsGoal (self , goal ):
45
+ return self .jar1 .water == goal or self .jar2 .water == goal
46
+
47
+
48
+ def checkio (volume1 , volume2 , goal ):
49
+ '''efficient breadth first search for minimal steps'''
50
+ initialJar1 = Jar ("1" , volume1 , 0 )
51
+ initialJar2 = Jar ("2" , volume2 , 0 )
52
+
53
+ states = [State (initialJar1 , initialJar2 , [])]
54
+
55
+ while True :
56
+ nextStates = []
57
+
58
+ for state in states :
59
+ prevStep = state .steps [- 1 ] if state .steps else ""
60
+
61
+ for jarA , jarB in itertools .permutations ((state .jar1 , state .jar2 )):
62
+
63
+ # if reasonable to fill jarA
64
+ if not jarA .isFull () and prevStep != jarA .drainStr ():
65
+ newState = State (
66
+ jarA .asFull (),
67
+ jarB ,
68
+ state .steps + [jarA .fillStr ()])
69
+
70
+ if newState .meetsGoal (goal ):
71
+ return newState .steps
72
+
73
+ nextStates .append (newState )
74
+
75
+ # if reasonable to drain jarA
76
+ if jarA .water and prevStep != jarA .fillStr ():
77
+ newState = State (
78
+ jarA .asEmpty (),
79
+ jarB ,
80
+ state .steps + [jarA .drainStr ()])
81
+
82
+ if newState .meetsGoal (goal ):
83
+ return newState .steps
84
+
85
+ nextStates .append (newState )
86
+
87
+ # if reasonable to pour water from jarA to jarB
88
+ if (jarA .water and not jarB .isFull ()
89
+ and prevStep != jarB .transferStr (jarA )
90
+ ):
91
+ transferAmount = min (jarA .water , jarB .volume - jarB .water )
92
+ newState = State (
93
+ jarA .withWaterChange (- transferAmount ),
94
+ jarB .withWaterChange (+ transferAmount ),
95
+ state .steps + [jarA .transferStr (jarB )])
96
+
97
+ if newState .meetsGoal (goal ):
98
+ return newState .steps
99
+
100
+ nextStates .append (newState )
101
+
102
+ states = nextStates
103
+
104
+
105
+ ################################################################################
106
+ # inefficient recursive depth first search approach
107
+
108
+ def checkioDepthFirstRecursive (volume1 , volume2 , goal ):
109
+ steps = depthFirstSearch (
110
+ goal ,
111
+ Jar ("1" , volume1 , 0 ),
112
+ Jar ("2" , volume2 , 0 ),
113
+ [],
114
+ {},
115
+ )
116
+
117
+ return steps
118
+
119
+
120
+ def depthFirstSearch (goal , jar1 , jar2 , steps , situationToNumSteps ):
121
+ situation = tuple (sorted ((jar1 , jar2 )))
122
+
123
+ if (situation in situationToNumSteps
124
+ and situationToNumSteps [situation ] <= len (steps )
125
+ ):
126
+ return []
127
+
128
+ situationToNumSteps [situation ] = len (steps )
129
+
130
+ if jar1 .water == goal or jar2 .water == goal :
131
+ return steps
132
+
133
+ stepBranches = []
134
+
135
+ for jarA , jarB in [(jar1 , jar2 ), (jar2 , jar1 )]:
136
+ # if reasonable to fill jarA
137
+ if jarA .water < jarA .volume :
138
+ stepBranches .append (depthFirstSearch (
139
+ goal ,
140
+ jarA .asFull (),
141
+ jarB ,
142
+ steps + [jarA .fillStr ()],
143
+ situationToNumSteps ,
144
+ ))
145
+
146
+ # if reasonable to drain jarA
147
+ if jarA .water :
148
+ stepBranches .append (depthFirstSearch (
149
+ goal ,
150
+ jarA .asEmpty (),
151
+ jarB ,
152
+ steps + [jarA .drainStr ()],
153
+ situationToNumSteps ,
154
+ ))
155
+
156
+ # if reasonable to pour from jarA to jarB
157
+ if jarA .water and jarB .water < jarB .volume :
158
+ transferAmount = min (jarA .water , jarB .volume - jarB .water )
159
+ stepBranches .append (depthFirstSearch (
160
+ goal ,
161
+ jarA .withWaterChange (- transferAmount ),
162
+ jarB .withWaterChange (+ transferAmount ),
163
+ steps + [jarA .transferStr (jarB )],
164
+ situationToNumSteps ,
165
+ ))
166
+
167
+ sortedStepBranches = sorted (
168
+ [branch for branch in stepBranches if branch ],
169
+ key = len )
170
+
171
+ if sortedStepBranches :
172
+ return sortedStepBranches [0 ]
173
+
174
+ return []
175
+
176
+
177
+ ################################################################################
178
+ # fun iterative algo that does not necessarily find minimal step sequence
179
+
180
+ class FunJar :
16
181
17
182
def __init__ (self , name , volume ):
18
183
self .name = name
@@ -40,13 +205,13 @@ def transfer(self, other):
40
205
return self .name + other .name
41
206
42
207
43
- def checkio (jar1Volume , jar2Volume , goal ):
208
+ def checkioNonMinimal (jar1Volume , jar2Volume , goal ):
44
209
if jar1Volume < jar2Volume :
45
- smallJar = Jar ("1" , jar1Volume )
46
- bigJar = Jar ("2" , jar2Volume )
210
+ smallJar = FunJar ("1" , jar1Volume )
211
+ bigJar = FunJar ("2" , jar2Volume )
47
212
else :
48
- smallJar = Jar ("2" , jar2Volume )
49
- bigJar = Jar ("1" , jar1Volume )
213
+ smallJar = FunJar ("2" , jar2Volume )
214
+ bigJar = FunJar ("1" , jar1Volume )
50
215
51
216
steps = []
52
217
@@ -67,8 +232,10 @@ def checkio(jar1Volume, jar2Volume, goal):
67
232
return None
68
233
69
234
235
+ ################################################################################
236
+ # self-checking
237
+
70
238
if __name__ == '__main__' :
71
- #This part is using only for self-checking and not necessary for auto-testing
72
239
def check_solution (func , initial_data , max_steps ):
73
240
first_volume , second_volume , goal = initial_data
74
241
actions = {
@@ -101,3 +268,4 @@ def check_solution(func, initial_data, max_steps):
101
268
102
269
assert check_solution (checkio , (5 , 7 , 6 ), 10 ), "Example"
103
270
assert check_solution (checkio , (3 , 4 , 1 ), 2 ), "One and two"
271
+
0 commit comments