Day 17 (Python)

Third time lucky!
... it was not worth staying up until 1am for this
This commit is contained in:
akp 2020-12-18 00:53:41 +00:00
parent cf15aee7f1
commit aed18d1ac8
No known key found for this signature in database
GPG key ID: D3E7EAA31B39637E
6 changed files with 112 additions and 148 deletions

2
.github/README.md vendored
View file

@ -32,7 +32,7 @@ Puzzle inputs and descriptions are not included in this repository. You'll have
| [14](/14-dockingData) | ![Completed][check] | [Link](/14-dockingData/python) | [Link](/14-dockingData/go) | | [14](/14-dockingData) | ![Completed][check] | [Link](/14-dockingData/python) | [Link](/14-dockingData/go) |
| [15](/15-rambunctiousRecitation) \* | ![Completed][check] | [Link](/15-rambunctiousRecitation/python) | [Link](/15-rambunctiousRecitation/go) | | [15](/15-rambunctiousRecitation) \* | ![Completed][check] | [Link](/15-rambunctiousRecitation/python) | [Link](/15-rambunctiousRecitation/go) |
| [16](/16-ticketTranslation) | ![Partially complete][partial] | [Link](/16-ticketTranslation/python) | | | [16](/16-ticketTranslation) | ![Partially complete][partial] | [Link](/16-ticketTranslation/python) | |
| [17](/17-conwayCubes) | ![Incomplete][cross] | | | | [17](/17-conwayCubes) | ![Partially complete][partial] | [Link](/17-conwayCubes/python) | |
| 18 | | | | | 18 | | | |
| 19 | | | | | 19 | | | |
| 20 | | | | | 20 | | | |

View file

@ -1,3 +1,21 @@
# [Day 17: Conway Cubes](https://adventofcode.com/2020/day/17) # [Day 17: Conway Cubes](https://adventofcode.com/2020/day/17)
Will be re-attempted at a later date. I would not have got this if it wasn't for [this Reddit comment](https://www.reddit.com/r/adventofcode/comments/kf5mzc/2020_day_17_i_am_now_sad/gg6u25j).
<details><summary>Script output</summary>
```
python .\python\
AoC 2020: day 17 - Conway Cubes
Python 3.8.5
Test cases
1.1 pass
2.1 pass
Answers
Part 1: 232
Part 2: 1620
```
</details>

View file

@ -9,6 +9,11 @@
"expected": 112 "expected": 112
} }
], ],
"two": [] "two": [
{
"input": ".#.\n..#\n###\n",
"expected": 848
}
]
} }
} }

View file

@ -1,49 +1,23 @@
from typing import List, Tuple from typing import Dict, Tuple, Callable
import math import itertools
active = "#" active_marker = "#"
inactive = "." inactive_marker = "."
class Matrix3D: translation_vectors_3d = [p for p in itertools.product([-1, 0, 1], repeat=3) if p != (0, 0, 0)]
master_array: List[List[List]] translation_vectors_4d = [p for p in itertools.product([-1, 0, 1], repeat=4) if p != (0, 0, 0, 0)]
x_zero: int
y_zero: int
z_zero: int
def __init__(self, x, y, z, default_value=None):
self.master_array = [[[default_value for _ in range(x)] for _ in range(y)] for _ in range(z)]
self.x_zero = math.floor(len(self.master_array[0][0]) / 2)
self.y_zero = math.floor(len(self.master_array[0]) / 2)
self.z_zero = math.floor(len(self.master_array) / 2)
def translate(self, x:int, y:int, z:int) -> Tuple[int, int, int]:
x += self.x_zero
y += self.y_zero
z += self.z_zero
return x, y, z
def __str__(self) -> str:
return str(self.master_array)
def __getitem__(self, location_tuple:Tuple[int, int, int]):
x, y, z = location_tuple
if x < 0 or y < 0 or z < 0:
raise IndexError("list index out of range")
return self.master_array[z][y][x]
def __setitem__(self, location_tuple:Tuple[int, int, int], value):
x, y, z = location_tuple
if x < 0 or y < 0 or z < 0:
raise IndexError("list index out of range")
self.master_array[z][y][x] = value
def parse(instr: str) -> int: def parse(instr: str, key:Callable[[int, int], int]) -> Dict[Tuple[int, int, int], str]:
input_lists = [[x for x in y] for y in instr.strip().split("\n")]
return 0 rtvl = {}
for y, row in enumerate(input_lists):
for x, col in enumerate(row):
if col != inactive_marker:
rtvl[key(x, y)] = col
return rtvl

View file

@ -1,115 +1,43 @@
from common import * from common import *
import copy from typing import Dict, Tuple
from pprint import pprint
iterations = 6 def count_neighbours(matrix:Dict[Tuple[int, int, int], str], position:Tuple[int, int, int]) -> int:
translation_vectors = [
(-1, 1, 1),
(0, 1, 1),
(1, 1, 1),
(-1, 0, 1),
(0, 0, 1),
(1, 0, 1),
(-1, -1, 1),
(0, -1, 1),
(1, -1, 1),
(-1, 1, -1),
(0, 1, -1),
(1, 1, -1),
(-1, 0, -1),
(0, 0, -1),
(1, 0, -1),
(-1, -1, -1),
(0, -1, -1),
(1, -1, -1),
(-1, 1, 0),
(0, 1, 0),
(1, 1, 0),
(-1, 0, 0),
(1, 0, 0),
(-1, -1, 0),
(0, -1, 0),
(1, -1, 0)
]
def find_neighbours(matrix:List[List[List[int]]], raw_point:Tuple[int, int, int]) -> int:
x, y, z = raw_point
num_neighbours = 0 num_neighbours = 0
for (vx, vy, vz) in translation_vectors: x, y, z = position
vx += x for (x_delta, y_delta, z_delta) in translation_vectors_3d:
vy += y if matrix.get((x + x_delta, y+y_delta, z+z_delta), inactive_marker) == active_marker:
vz += z
if vx < 0 or vy < 0 or vz < 0:
continue
try:
current_val = matrix[vz][vy][vx]
except IndexError:
continue
if current_val == active:
num_neighbours += 1 num_neighbours += 1
return num_neighbours return num_neighbours
def iterate(matrix:List[List[List[int]]]) -> List[List[List[int]]]:
new = copy.deepcopy(matrix) def iterate(matrix:Dict[Tuple[int, int, int], str]) -> Dict[Tuple[int, int, int], str]:
sz = len(matrix) new = {}
sy = len(matrix[0])
sx = len(matrix[0][0]) # get min/max for x, y and z values
for z in range(sz): keys = list(matrix)
for y in range(sy): _, _, min_z = min(keys, key=lambda x: x[2])
for x in range(sx): _, _, max_z = max(keys, key=lambda x: x[2])
neighbours = find_neighbours(matrix, (x, y, z)) _, min_y, _ = min(keys, key=lambda x: x[1])
current_state = matrix[z][y][x] _, max_y, _ = max(keys, key=lambda x: x[1])
if (neighbours == 2 or neighbours == 3) and current_state != active: min_x, _, _ = min(keys, key=lambda x: x[0])
new[z][x][y] = inactive max_x, _, _ = max(keys, key=lambda x: x[0])
elif neighbours == 3 and current_state == inactive:
new[z][x][y] = active for z in range(min_z - 2, max_z + 2):
for y in range(min_y - 2, max_y + 2):
for x in range(min_x - 2, max_x + 2):
num_neighbours = count_neighbours(matrix, (x, y, z))
current_state = matrix.get((x, y, z), inactive_marker)
if (num_neighbours == 2 and current_state == active_marker) or num_neighbours == 3:
new[(x, y, z)] = active_marker
return new return new
def partOne(instr: str) -> int: def partOne(instr: str) -> int:
matrix = parse(instr, lambda x, y: (x, y, 0))
input_array = [[y for y in x] for x in instr.strip().split("\n")] for _ in range(6):
assert len(input_array) == len(input_array[0]), "input array must be square"
input_size = len(input_array)
# determine matrix size and create
size = (iterations * 2) + 1 + len(input_array) # 1 extra col/row/whatever around per iteration
if size % 2 != len(input_array) % 2:
size += 1 # the len and size must both be even or odd
matrix = [[[inactive for _ in range(size)] for _ in range(size)] for _ in range(size)]
# Load input into center of matrix
centerpoint = size / 2
centerpoint_mod = len(input_array) / 2
start_point = int((centerpoint - centerpoint_mod) - 1)
center_z = int(size / 2) - 1
for y in range(input_size):
for x in range(input_size):
matrix[center_z][y + start_point][x + start_point] = input_array[y][x]
# Iterate
for _ in range(iterations):
matrix = iterate(matrix) matrix = iterate(matrix)
# Count active return len(matrix)
active_count = 0
sz = len(matrix)
sy = len(matrix[0])
sx = len(matrix[0][0])
for z in range(sz):
for y in range(sy):
for x in range(sx):
if matrix[z][y][x] == active:
active_count += 1
return active_count

View file

@ -1,6 +1,45 @@
from common import * from common import *
def count_neighbours(matrix:Dict[Tuple[int, int, int, int], str], position:Tuple[int, int, int, int]) -> int:
num_neighbours = 0
x, y, z, w = position
for (x_delta, y_delta, z_delta, w_delta) in translation_vectors_4d:
if matrix.get((x + x_delta, y+y_delta, z+z_delta, w+w_delta), inactive_marker) == active_marker:
num_neighbours += 1
return num_neighbours
def iterate(matrix:Dict[Tuple[int, int, int], str]) -> Dict[Tuple[int, int, int], str]:
new = {}
# get min/max for x, y and z values
keys = list(matrix)
_, _, _, min_w = min(keys, key=lambda x: x[3])
_, _, _, max_w = max(keys, key=lambda x: x[3])
_, _, min_z, _ = min(keys, key=lambda x: x[2])
_, _, max_z, _ = max(keys, key=lambda x: x[2])
_, min_y, _, _ = min(keys, key=lambda x: x[1])
_, max_y, _, _ = max(keys, key=lambda x: x[1])
min_x, _, _, _ = min(keys, key=lambda x: x[0])
max_x, _, _, _ = max(keys, key=lambda x: x[0])
for w in range(min_w - 2, max_w + 2):
for z in range(min_z - 2, max_z + 2):
for y in range(min_y - 2, max_y + 2):
for x in range(min_x - 2, max_x + 2):
num_neighbours = count_neighbours(matrix, (x, y, z, w))
current_state = matrix.get((x, y, z, w), inactive_marker)
if (num_neighbours == 2 and current_state == active_marker) or num_neighbours == 3:
new[(x, y, z, w)] = active_marker
return new
def partTwo(instr: str) -> int: def partTwo(instr: str) -> int:
input_list = parse(instr) matrix = parse(instr, lambda x, y: (x, y, 0, 0))
return 0
for _ in range(6):
matrix = iterate(matrix)
return len(matrix)