diff --git a/.github/README.md b/.github/README.md index f736749..89a370f 100644 --- a/.github/README.md +++ b/.github/README.md @@ -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) | | [15](/15-rambunctiousRecitation) \* | ![Completed][check] | [Link](/15-rambunctiousRecitation/python) | [Link](/15-rambunctiousRecitation/go) | | [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 | | | | | 19 | | | | | 20 | | | | diff --git a/17-conwayCubes/README.md b/17-conwayCubes/README.md index 77febb0..bc8f0cd 100644 --- a/17-conwayCubes/README.md +++ b/17-conwayCubes/README.md @@ -1,3 +1,21 @@ # [Day 17: Conway Cubes](https://adventofcode.com/2020/day/17) -Will be re-attempted at a later date. \ No newline at end of file +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). + +
Script output + +``` +❯ 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 +``` + +
\ No newline at end of file diff --git a/17-conwayCubes/info.json b/17-conwayCubes/info.json index ca4e06f..742822e 100644 --- a/17-conwayCubes/info.json +++ b/17-conwayCubes/info.json @@ -9,6 +9,11 @@ "expected": 112 } ], - "two": [] + "two": [ + { + "input": ".#.\n..#\n###\n", + "expected": 848 + } + ] } } \ No newline at end of file diff --git a/17-conwayCubes/python/common.py b/17-conwayCubes/python/common.py index a51c1f0..6ce21bc 100644 --- a/17-conwayCubes/python/common.py +++ b/17-conwayCubes/python/common.py @@ -1,49 +1,23 @@ -from typing import List, Tuple -import math +from typing import Dict, Tuple, Callable +import itertools -active = "#" -inactive = "." +active_marker = "#" +inactive_marker = "." -class Matrix3D: - master_array: List[List[List]] - 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 +translation_vectors_3d = [p for p in itertools.product([-1, 0, 1], repeat=3) if p != (0, 0, 0)] +translation_vectors_4d = [p for p in itertools.product([-1, 0, 1], repeat=4) if p != (0, 0, 0, 0)] -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 \ No newline at end of file diff --git a/17-conwayCubes/python/partOne.py b/17-conwayCubes/python/partOne.py index c6b4f40..85c9761 100644 --- a/17-conwayCubes/python/partOne.py +++ b/17-conwayCubes/python/partOne.py @@ -1,115 +1,43 @@ from common import * -import copy -from pprint import pprint +from typing import Dict, Tuple -iterations = 6 - -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 +def count_neighbours(matrix:Dict[Tuple[int, int, int], str], position:Tuple[int, int, int]) -> int: num_neighbours = 0 - for (vx, vy, vz) in translation_vectors: - vx += x - vy += y - 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: + x, y, z = position + for (x_delta, y_delta, z_delta) in translation_vectors_3d: + if matrix.get((x + x_delta, y+y_delta, z+z_delta), inactive_marker) == active_marker: num_neighbours += 1 return num_neighbours -def iterate(matrix:List[List[List[int]]]) -> List[List[List[int]]]: - new = copy.deepcopy(matrix) - 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): - neighbours = find_neighbours(matrix, (x, y, z)) - current_state = matrix[z][y][x] - if (neighbours == 2 or neighbours == 3) and current_state != active: - new[z][x][y] = inactive - elif neighbours == 3 and current_state == inactive: - new[z][x][y] = active + +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_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 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 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")] - - 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): + for _ in range(6): matrix = iterate(matrix) - # Count active - 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 - + return len(matrix) diff --git a/17-conwayCubes/python/partTwo.py b/17-conwayCubes/python/partTwo.py index bdfb098..651a88c 100644 --- a/17-conwayCubes/python/partTwo.py +++ b/17-conwayCubes/python/partTwo.py @@ -1,6 +1,45 @@ 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: - input_list = parse(instr) - return 0 + matrix = parse(instr, lambda x, y: (x, y, 0, 0)) + + for _ in range(6): + matrix = iterate(matrix) + + return len(matrix)