diff --git a/.github/README.md b/.github/README.md index c7ccef8..4183d8e 100644 --- a/.github/README.md +++ b/.github/README.md @@ -14,33 +14,33 @@ Puzzle inputs and descriptions are not included in this repository. You'll have -| Day | | Python | Go | -| --------------------------- | ----------------------------- | ------------------------------------- | --------------------------------- | -| [1](/01-reportRepair) | ![Completed][check] | [Link](/01-reportRepair/python) | [Link](/01-reportRepair/go) | -| [2](/02-passwordPhilosophy) | ![Completed][check] | [Link](/02-passwordPhilosophy/python) | [Link](/02-passwordPhilosophy/go) | -| [3](/03-tobogganTrajectory) | ![Completed][check] | [Link](/03-tobogganTrajectory/python) | [Link](/03-tobogganTrajectory/go) | -| [4](/04-passportProcessing) | ![Completed][check] | [Link](/04-passportProcessing/python) | [Link](/04-passportProcessing/go) | -| [5](/05-binaryBoarding) | ![Completed][check] | [Link](/05-binaryBoarding/python) | [Link](/05-binaryBoarding/go) | -| [6](/06-customCustoms) | ![Completed][check] | [Link](/06-customCustoms/python) | [Link](/06-customCustoms/go) | -| [7](/07-handyHaversacks) | ![Completed][check] | [Link](/07-handyHaversacks/python) | [Link](/07-handyHaversacks/go) | -| [8](/08-handheldHalting) | ![Completed][check] | [Link](/08-handheldHalting/python) | [Link](/08-handheldHalting/go) | -| [9](/09-encodingError) | ![Completed][check] | [Link](/09-encodingError/python) | [Link](/09-encodingError/go) | -| [10](/10-adapterArray) | ![Completed][check] | [Link](/10-adapterArray/python) | [Link](/10-adapterArray/go) | -| 11 | ![Not yet attempted][pending] | | | -| 12 | | | | -| 13 | | | | -| 14 | | | | -| 15 | | | | -| 16 | | | | -| 17 | | | | -| 18 | | | | -| 19 | | | | -| 20 | | | | -| 21 | | | | -| 22 | | | | -| 23 | | | | -| 24 | | | | -| 25 | | | | +| Day | | Python | Go | +| --------------------------- | ------------------------------ | ------------------------------------- | --------------------------------- | +| [1](/01-reportRepair) | ![Completed][check] | [Link](/01-reportRepair/python) | [Link](/01-reportRepair/go) | +| [2](/02-passwordPhilosophy) | ![Completed][check] | [Link](/02-passwordPhilosophy/python) | [Link](/02-passwordPhilosophy/go) | +| [3](/03-tobogganTrajectory) | ![Completed][check] | [Link](/03-tobogganTrajectory/python) | [Link](/03-tobogganTrajectory/go) | +| [4](/04-passportProcessing) | ![Completed][check] | [Link](/04-passportProcessing/python) | [Link](/04-passportProcessing/go) | +| [5](/05-binaryBoarding) | ![Completed][check] | [Link](/05-binaryBoarding/python) | [Link](/05-binaryBoarding/go) | +| [6](/06-customCustoms) | ![Completed][check] | [Link](/06-customCustoms/python) | [Link](/06-customCustoms/go) | +| [7](/07-handyHaversacks) | ![Completed][check] | [Link](/07-handyHaversacks/python) | [Link](/07-handyHaversacks/go) | +| [8](/08-handheldHalting) | ![Completed][check] | [Link](/08-handheldHalting/python) | [Link](/08-handheldHalting/go) | +| [9](/09-encodingError) | ![Completed][check] | [Link](/09-encodingError/python) | [Link](/09-encodingError/go) | +| [10](/10-adapterArray) | ![Completed][check] | [Link](/10-adapterArray/python) | [Link](/10-adapterArray/go) | +| [11](/11-seatingSystem) | ![Partially complete][partial] | [Link](/11-seatingSystem/python) | | +| 12 | | | | +| 13 | | | | +| 14 | | | | +| 15 | | | | +| 16 | | | | +| 17 | | | | +| 18 | | | | +| 19 | | | | +| 20 | | | | +| 21 | | | | +| 22 | | | | +| 23 | | | | +| 24 | | | | +| 25 | | | | diff --git a/11-seatingSystem/README.md b/11-seatingSystem/README.md new file mode 100644 index 0000000..52167e3 --- /dev/null +++ b/11-seatingSystem/README.md @@ -0,0 +1,21 @@ +# [Day 11: Seating System](https://adventofcode.com/2020/day/11) + +This is a [cellular automata](https://en.wikipedia.org/wiki/Cellular_automaton) that (in part one) uses the [Moore neighbourhood](https://en.wikipedia.org/wiki/Moore_neighborhood). + +
Script output + +``` +❯ python .\python\ +AoC 2020: day 11 - Seating System +Python 3.8.5 + +Test cases +1.1 pass +2.1 pass + +Answers +Part 1: 2283 +Part 2: 2054 +``` + +
diff --git a/11-seatingSystem/info.json b/11-seatingSystem/info.json new file mode 100644 index 0000000..6eee3d5 --- /dev/null +++ b/11-seatingSystem/info.json @@ -0,0 +1,19 @@ +{ + "year": "2020", + "day": "11", + "title": "Seating System", + "testCases": { + "one": [ + { + "input": "L.LL.LL.LL\nLLLLLLL.LL\nL.L.L..L..\nLLLL.LL.LL\nL.LL.LL.LL\nL.LLLLL.LL\n..L.L.....\nLLLLLLLLLL\nL.LLLLLL.L\nL.LLLLL.LL\n", + "expected": 37 + } + ], + "two": [ + { + "input": "L.LL.LL.LL\nLLLLLLL.LL\nL.L.L..L..\nLLLL.LL.LL\nL.LL.LL.LL\nL.LLLLL.LL\n..L.L.....\nLLLLLLLLLL\nL.LLLLLL.L\nL.LLLLL.LL\n", + "expected": 26 + } + ] + } +} \ No newline at end of file diff --git a/11-seatingSystem/python/__main__.py b/11-seatingSystem/python/__main__.py new file mode 100644 index 0000000..d7e125b --- /dev/null +++ b/11-seatingSystem/python/__main__.py @@ -0,0 +1,69 @@ +import json +import platform +import sys + +from rich import print + +from partOne import partOne +from partTwo import partTwo + + +def run_tests(test_cases): + do_tests = True + if len(test_cases) == 0: + do_tests = False + elif len(test_cases["one"]) == 0 and len(test_cases["two"]) == 0: + do_tests = False + + if not do_tests: + print("Info: no test cases specified. Skipping tests\n") + return + + print("Test cases") + + def rt(tcs, f, n): + for i, tc in enumerate(tcs): + print(f"{n}.{i+1} ", end="") + expectedInt = tc["expected"] + result = f(str(tc["input"])) + if result == expectedInt: + print("[green]pass[/green]") + else: + print(f"[red]fail[/red] (got {result}, expected {expectedInt})") + + rt(test_cases["one"], partOne, 1) + rt(test_cases["two"], partTwo, 2) + + print() + + +if __name__ == "__main__": + try: + info = open("info.json").read() + except FileNotFoundError: + print("Error: could not open info.json") + sys.exit(-1) + + info = json.loads(info) + + year = info["year"] + day = info["day"] + title = info["title"] + + print(f"[yellow]AoC {year}[/yellow]: day {day} - {title}") + print(f"Python {platform.python_version()}\n") + + try: + challenge_input = open("input.txt").read() + except FileNotFoundError: + print("Error: could not open input.txt") + sys.exit(-1) + + run_tests(info["testCases"]) + + if "debug" in sys.argv: + sys.exit() + + print("Answers") + print("Part 1:", partOne(challenge_input)) + print("Part 2:", partTwo(challenge_input)) diff --git a/11-seatingSystem/python/common.py b/11-seatingSystem/python/common.py new file mode 100644 index 0000000..c8449a1 --- /dev/null +++ b/11-seatingSystem/python/common.py @@ -0,0 +1,53 @@ +import copy +import time +from typing import Callable, List + + +open_seat = "L" +filled_seat = "#" +no_seat = "." + + +def parse(instr: str) -> List[List[str]]: + return [[char for char in x] for x in instr.strip().split("\n")] + +def iterate(current_hall: List[List[str]], neighbour_counter: Callable, get_new_state: Callable) -> List[List[str]]: + # Copy hall + next_hall = copy.deepcopy(current_hall) # this is bloody slow + + hall_size = (len(next_hall[0]), len(next_hall)) + + # Iterate each chair space + for col in range(hall_size[0]): + for row in range(hall_size[1]): + current_pos = current_hall[row][col] + + # If it's the floor, there's nothing we can do with this spot + if current_pos == no_seat: + continue + + # Count number of adjacent seats + num_neighbours = neighbour_counter(current_hall, (row, col), hall_size) + + # Execute rules on copied list based on that count + next_hall[row][col] = get_new_state(num_neighbours, next_hall[row][col]) + + # Return copied list + return next_hall + +def run(current_state:List[List[str]], neighbour_counter:Callable, get_new_state:Callable) -> int: + last_state = None + + while current_state != last_state: + last_state = current_state + current_state = iterate(current_state, neighbour_counter, get_new_state) + + # at this point, we've ended up with two identical seating arrangements + # let's count the number of occupied seats + total_occupied = 0 + for a in current_state: + for b in a: + if b == filled_seat: + total_occupied += 1 + + return total_occupied \ No newline at end of file diff --git a/11-seatingSystem/python/partOne.py b/11-seatingSystem/python/partOne.py new file mode 100644 index 0000000..feada67 --- /dev/null +++ b/11-seatingSystem/python/partOne.py @@ -0,0 +1,42 @@ +from typing import List, Tuple + +from common import * + + +def get_new_state(num_neighbours:int, old_state:str) -> str: + if num_neighbours == 0: + return filled_seat + elif num_neighbours >= 4 and old_state == filled_seat: + return open_seat + + return old_state + + +def count_neighbours(hall:List[List[str]], current_pos:Tuple[int, int], hall_size:Tuple[int, int]) -> int: + num_neighbours = 0 + check_positions = [ + (0, 1), + (1, 1), + (1, 0), + (1, -1), + (0, -1), + (-1, -1), + (-1, 0), + (-1, 1), + ] + + row, col = current_pos + + for (x, y) in check_positions: + test_x_pos = x + col + test_y_pos = y + row + + if 0 <= test_x_pos < hall_size[0] and 0 <= test_y_pos < hall_size[1]: + if hall[test_y_pos][test_x_pos] == filled_seat: + num_neighbours += 1 + + return num_neighbours + + +def partOne(instr: str) -> int: + return run(parse(instr), count_neighbours, get_new_state) diff --git a/11-seatingSystem/python/partTwo.py b/11-seatingSystem/python/partTwo.py new file mode 100644 index 0000000..e30b7ae --- /dev/null +++ b/11-seatingSystem/python/partTwo.py @@ -0,0 +1,52 @@ +from typing import List, Tuple + +from common import * + + +def get_new_state(num_neighbours:int, old_state:str) -> str: + if num_neighbours == 0: + return filled_seat + elif num_neighbours >= 5 and old_state == filled_seat: + return open_seat + + return old_state + + +def count_neighbours(hall:List[List[str]], current_pos:Tuple[int, int], hall_size:Tuple[int, int]) -> int: + num_neighbours = 0 + deltas = [ + (0, 1), + (1, 1), + (1, 0), + (1, -1), + (0, -1), + (-1, -1), + (-1, 0), + (-1, 1), + ] + + row, col = current_pos + + for (x, y) in deltas: + + test_x_pos = col + x + test_y_pos = row + y + + while 0 <= test_x_pos < hall_size[0] and 0 <= test_y_pos < hall_size[1]: + + test_location = hall[test_y_pos][test_x_pos] + + if test_location == filled_seat: + num_neighbours += 1 + + if test_location in [open_seat, filled_seat]: + break + + test_x_pos += x + test_y_pos += y + + return num_neighbours + + +def partTwo(instr: str) -> int: + return run(parse(instr), count_neighbours, get_new_state)