Skip to content

Commit 7a57251

Browse files
author
jegner
committed
triple solve water-jars from ice base island
1 parent da90f13 commit 7a57251

File tree

1 file changed

+175
-7
lines changed

1 file changed

+175
-7
lines changed

water-jars.py

Lines changed: 175 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,175 @@
99
1010
for latest versions of my solutions, see my checkio solution github repo:
1111
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
1217
'''
1318

1419

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:
16181

17182
def __init__(self, name, volume):
18183
self.name = name
@@ -40,13 +205,13 @@ def transfer(self, other):
40205
return self.name + other.name
41206

42207

43-
def checkio(jar1Volume, jar2Volume, goal):
208+
def checkioNonMinimal(jar1Volume, jar2Volume, goal):
44209
if jar1Volume < jar2Volume:
45-
smallJar = Jar("1", jar1Volume)
46-
bigJar = Jar("2", jar2Volume)
210+
smallJar = FunJar("1", jar1Volume)
211+
bigJar = FunJar("2", jar2Volume)
47212
else:
48-
smallJar = Jar("2", jar2Volume)
49-
bigJar = Jar("1", jar1Volume)
213+
smallJar = FunJar("2", jar2Volume)
214+
bigJar = FunJar("1", jar1Volume)
50215

51216
steps = []
52217

@@ -67,8 +232,10 @@ def checkio(jar1Volume, jar2Volume, goal):
67232
return None
68233

69234

235+
################################################################################
236+
# self-checking
237+
70238
if __name__ == '__main__':
71-
#This part is using only for self-checking and not necessary for auto-testing
72239
def check_solution(func, initial_data, max_steps):
73240
first_volume, second_volume, goal = initial_data
74241
actions = {
@@ -101,3 +268,4 @@ def check_solution(func, initial_data, max_steps):
101268

102269
assert check_solution(checkio, (5, 7, 6), 10), "Example"
103270
assert check_solution(checkio, (3, 4, 1), 2), "One and two"
271+

0 commit comments

Comments
 (0)