Solve problem 126 which is a 55 percent one. Yay.
parent
927cd2011b
commit
09f7e27c16
|
@ -0,0 +1,141 @@
|
|||
from functools import lru_cache
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
CUBE_THRESHOLD = 10000
|
||||
|
||||
def base_cubes(xlen, ylen, zlen) -> set:
|
||||
r = set()
|
||||
for x in range(xlen):
|
||||
for y in range(ylen):
|
||||
for z in range(zlen):
|
||||
r.add((x, y, z))
|
||||
return r
|
||||
|
||||
|
||||
@lru_cache
|
||||
def neighbors(x, y, z) -> list:
|
||||
dirs = [
|
||||
(1, 0, 0),
|
||||
(-1, 0, 0),
|
||||
(0, 1, 0),
|
||||
(0, -1, 0),
|
||||
(0, 0, 1),
|
||||
(0, 0, -1),
|
||||
]
|
||||
return [(x + dx, y + dy, z + dz) for dx, dy, dz in dirs]
|
||||
|
||||
|
||||
@lru_cache
|
||||
def count_cubes_per_layer(x, y, z) -> list:
|
||||
""" First iteration. Naively add outer layers. """
|
||||
r = []
|
||||
clayer = base_cubes(x, y, z)
|
||||
players = set(clayer)
|
||||
while True:
|
||||
nlayer = set()
|
||||
for c in clayer:
|
||||
for nb in neighbors(*c):
|
||||
if nb not in players:
|
||||
nlayer.add(nb)
|
||||
clayer = nlayer
|
||||
players |= clayer
|
||||
layer_count = len(nlayer)
|
||||
r.append(layer_count)
|
||||
if layer_count > CUBE_THRESHOLD:
|
||||
break
|
||||
return r
|
||||
|
||||
|
||||
def count_cubes_per_layer2(x, y, z) -> list:
|
||||
""" Second iteration. Realized that the delta between the layers grows by
|
||||
the same value. """
|
||||
r = []
|
||||
clayer = base_cubes(x, y, z)
|
||||
players = set(clayer)
|
||||
layer_0 = 0
|
||||
layer_1 = 0
|
||||
for i in range(2):
|
||||
nlayer = set()
|
||||
for c in clayer:
|
||||
for nb in neighbors(*c):
|
||||
if nb not in players:
|
||||
nlayer.add(nb)
|
||||
clayer = nlayer
|
||||
players |= clayer
|
||||
layer_count = len(nlayer)
|
||||
if i == 0:
|
||||
layer_0 = layer_count
|
||||
elif i == 1:
|
||||
layer_1 = layer_count
|
||||
r.append(layer_count)
|
||||
if layer_count > CUBE_THRESHOLD:
|
||||
break
|
||||
|
||||
layer = r[-1]
|
||||
layer_delta = (layer_1 - layer_0) + 8
|
||||
while layer <= CUBE_THRESHOLD:
|
||||
layer += layer_delta
|
||||
r.append(layer)
|
||||
layer_delta += 8
|
||||
return r
|
||||
|
||||
|
||||
def count_cubes_per_layer3(x, y, z) -> list:
|
||||
""" Third iteration. Calculate first two layers directly, and then use
|
||||
constant delta approach from second iteration. """
|
||||
r = []
|
||||
layer_0 = 2 * (x * y + x * z + y * z)
|
||||
layer_1 = layer_0 + 4 * (x + y + z)
|
||||
r = [layer_0, layer_1]
|
||||
layer = r[-1]
|
||||
layer_delta = (layer_1 - layer_0) + 8
|
||||
while layer <= CUBE_THRESHOLD:
|
||||
layer += layer_delta
|
||||
r.append(layer)
|
||||
layer_delta += 8
|
||||
return r
|
||||
|
||||
|
||||
def euler_126():
|
||||
# We assume that the least n for which C(n) is equal to `target`
|
||||
# is under `layer_min_threshold`. We got this my trial and error.
|
||||
layer_min_threshold = 20000
|
||||
target = 1000
|
||||
|
||||
layers = defaultdict(int)
|
||||
for x in range(1, layer_min_threshold):
|
||||
for y in range(1, x + 1):
|
||||
|
||||
# If we exceed the threshold, cut the loop short.
|
||||
if 2 * x * y > layer_min_threshold:
|
||||
break
|
||||
|
||||
for z in range(1, y + 1):
|
||||
layer_0 = 2 * (x * y + x * z + y * z)
|
||||
layer_1 = layer_0 + 4 * (x + y + z)
|
||||
|
||||
layers[layer_0] += 1
|
||||
layers[layer_1] += 1
|
||||
|
||||
layer = layer_1
|
||||
layer_delta = (layer_1 - layer_0) + 8
|
||||
|
||||
# If we exceed the threshold, cut the loop short.
|
||||
while layer < layer_min_threshold:
|
||||
layer += layer_delta
|
||||
layer_delta += 8
|
||||
layers[layer] += 1
|
||||
|
||||
# Find the actual least value n for which C(n) == target.
|
||||
layermin = 10**9
|
||||
for n in layers.keys():
|
||||
if layers[n] == target and n < layermin:
|
||||
layermin = n
|
||||
return layermin
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
solution = euler_126()
|
||||
print("e126.py: " + str(solution))
|
||||
assert(solution == 18522)
|
Loading…
Reference in New Issue