/
s1.py
168 lines (134 loc) · 4.56 KB
/
s1.py
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
## Strawberry Fields
import common
import s0
import copy
import doctest
import itertools
import sys
import time
# Helpers
def unrotate(field):
"""
unrotate undo the effects of rotate on <field>.
"""
return flip_hz(flip_vt(rotate(field)))
def rotate(field):
"""
rotates <field> 90 degrees.
"""
result = []
# make the resulting structure
width, height = len(field[0]), len(field)
row = [0] * height
for w in xrange(width):
result.append(copy.deepcopy(row))
# copy data from field to result
for ri in xrange(len(field)):
field[ri].reverse()
for ci in xrange(len(field[ri])):
result[ci][ri] = field[ri][ci]
return result
def flip_vt(field):
"""
flip_vt flips <field> vertically.
"""
field.reverse()
return field
def flip_hz(field):
"""
flip_hz flips <field> horizontally.
"""
result = []
for row in field:
row.reverse()
result.append(row)
return result
def variant_reduction(puzzle):
"""
variant_reduction flips and rotates <puzzle> to find a better solution.
"""
max, field = puzzle
# there are 8 variations to each puzzle, these 4 + 4 rotated ones
variants = [lambda f: f, lambda f: flip_hz(f), lambda f: flip_vt(f), lambda f: flip_vt(flip_hz(f))]
rotated_field = rotate(copy.deepcopy(field))
best_cost, best_field = sys.maxint, None
for vi in xrange(4):
_, _field = simple_reduction((max, variants[vi](copy.deepcopy(field))))
if common.cost(_field) < best_cost:
best_cost, best_field = common.cost(_field), variants[vi](_field)
_, _field = simple_reduction((max, variants[vi](copy.deepcopy(rotated_field))))
if common.cost(_field) < best_cost:
best_cost, best_field = common.cost(_field), unrotate(variants[vi](_field))
return max, best_field
def simple_reduction(puzzle):
"""
simple_reduction returns a solution to <puzzle>.
It works by reducing the number of greenhouses one by one until it has the
lowest cost and meets the max constraint.
"""
max, field = puzzle
# figure out the current number of greenhouses
greenhouses = common.ids(field)
# we need to keep a copy of the previous field and it's cost in order
# to return it once we've realized we've done one reduction too many
prev_field, prev_cost = None, sys.maxint
if len(greenhouses) <= max:
prev_field, prev_cost = copy.deepcopy(field), common.cost(field)
# join greenhouses until when run out of them or until max constraint
# is met *and* cost increases from one reduction to the next
while len(greenhouses) > 1:
j1, j2, js = 0, 0, sys.maxint
# try each combination of greenhouses
for g1, g2 in itertools.combinations(greenhouses, 2):
# find outer bounds (left, right, top and bottom) for a greenhouse made
# up of g1 and g2
size3, p31, p32 = common.outer_bounds([g1, g2], field)
if size3 is not None:
size1, p11, p12 = common.outer_bounds(g1, field)
size2, p21, p22 = common.outer_bounds(g2, field)
diff = size3 - size2 - size1
if diff < js:
j1, j2, js = g1, g2, diff
# if we run out of combinations to try
# we must either surrender (return None)
# or if len(greenhouses) <= max return
# the best solution we have.
if j1 == 0:
if len(greenhouses) <= max:
return max, prev_field
else:
return max, None
# join j1 and j2, remove j2 from greenhouses
field = common.join(j1, j2, field)
greenhouses.remove(j2)
# decide if we should exit this loop or keep on reducing
curr_cost = common.cost(field)
if len(greenhouses) < max:
if prev_cost < curr_cost:
return max, prev_field
prev_field, prev_cost = copy.deepcopy(field), curr_cost
# if we end up here, we've come down to 1 greenhouse
return max, field
def solve(filename):
"""
solve prints out solutions to each of the fields described in <filename>.
"""
tstart = time.time()
count, total = 0, 0
for puzzle in common.parse_file(filename):
pstart = time.time()
max, field = variant_reduction(s0.join_vertically(s0.join_horizontally(s0.identify(puzzle))))
count += 1
total += common.cost(field)
print "cost:", common.cost(field), "time:", time.strftime("%H:%M:%S", time.gmtime(time.time() - pstart))
print common.format(field)
print "%s field(s). Total cost is $%s" % (count, total)
print time.strftime("Total time is %H:%M:%S", time.gmtime(time.time() - tstart))
if __name__ == "__main__":
if len(sys.argv[1:]) == 0:
print "Running doctests."
doctest.testmod()
doctest.testfile("tests.text")
else:
for f in sys.argv[1:]:
solve(f)