diff --git a/.github/README.md b/.github/README.md index 9544b89..f499e4e 100644 --- a/.github/README.md +++ b/.github/README.md @@ -29,7 +29,7 @@ Puzzle inputs and descriptions are not included in this repository. You'll have | [11](/11-seatingSystem) \* | ![Completed][check] | [Link](/11-seatingSystem/python) | [Link](/11-seatingSystem/python) | | [12](/12-rainRisk) \* | ![Completed][check] | [Link](/12-rainRisk/python) | [Link](/12-rainRisk/go) | | [13](/13-shuttleSearch) | ![Partially complete][partial] | [Link](/13-shuttleSearch/python) | | -| 14 | ![Not yet attempted][pending] | | | +| [14](/14-dockingData) | ![Partially complete][partial] | [Link](/14-dockingData/python) | | | 15 | | | | | 16 | | | | | 17 | | | | diff --git a/14-dockingData/README.md b/14-dockingData/README.md new file mode 100644 index 0000000..3856a1c --- /dev/null +++ b/14-dockingData/README.md @@ -0,0 +1,19 @@ +# [Day 14: Docking Data](https://adventofcode.com/2020/day/14) + +
Script output + +``` +❯ python .\python\ +AoC 2020: day 14 - Docking Data +Python 3.8.5 + +Test cases +1.1 pass +2.1 pass + +Answers +Part 1: 8570568288597 +Part 2: 3289441921203 +``` + +
diff --git a/14-dockingData/info.json b/14-dockingData/info.json new file mode 100644 index 0000000..7e44fa0 --- /dev/null +++ b/14-dockingData/info.json @@ -0,0 +1,19 @@ +{ + "year": "2020", + "day": "14", + "title": "Docking Data", + "testCases": { + "one": [ + { + "input": "mask = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXX0X\nmem[8] = 11\nmem[7] = 101\nmem[8] = 0\n", + "expected": 165 + } + ], + "two": [ + { + "input": "mask = 000000000000000000000000000000X1001X\nmem[42] = 100\nmask = 00000000000000000000000000000000X0XX\nmem[26] = 1\n", + "expected": 208 + } + ] + } +} \ No newline at end of file diff --git a/14-dockingData/python/__main__.py b/14-dockingData/python/__main__.py new file mode 100644 index 0000000..da7ce4c --- /dev/null +++ b/14-dockingData/python/__main__.py @@ -0,0 +1,77 @@ +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) + + if "vis" in sys.argv: + import visualise + + print("[green]Running visualisation....[/green]") + + visualise.visualise(challenge_input) + sys.exit() + + 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/14-dockingData/python/common.py b/14-dockingData/python/common.py new file mode 100644 index 0000000..e1357c8 --- /dev/null +++ b/14-dockingData/python/common.py @@ -0,0 +1,42 @@ +from typing import List, Tuple +import re +import itertools + + +class Instruction: + address: int + value: int + mask: str + + def __init__(self, instruction: str, mask: str): + if mask is None: + raise ValueError(f"bad mask") + m = re.match(r"mem\[(\d+)\] ?= ?(\d+)", instruction) + if m is None: + raise ValueError(f"unable to parse input instruction '{instruction}'") + self.mask = mask + self.address = int(m.group(1)) + self.value = int(m.group(2)) + + +def parse(instr: str) -> List[Instruction]: + retval = [] + + current_mask = None + for line in instr.strip().split("\n"): + if "mask" in line: + current_mask = line.split("=")[-1].strip() + else: + retval.append(Instruction(line, current_mask)) + + return retval + + +def number_to_base(n: int, b: int) -> str: + if n == 0: + return "0" + digits = [] + while n: + digits.append(int(n % b)) + n //= b + return "".join([str(x) for x in digits[::-1]]) diff --git a/14-dockingData/python/partOne.py b/14-dockingData/python/partOne.py new file mode 100644 index 0000000..1da9a54 --- /dev/null +++ b/14-dockingData/python/partOne.py @@ -0,0 +1,28 @@ +from common import * + + +def apply_mask(number: int, mask: str) -> int: + # There has to be a better way to do this... but oh well + # it works, after all + value = number_to_base(number, 2).zfill(len(mask)) + combi = "" + for (v, m) in zip(value, mask.lower()): + if m == "x": + combi += v + else: + combi += m + return int(combi, 2) + + +def partOne(instr: str) -> int: + input_instructions = parse(instr) + memory = {} + + for instruction in input_instructions: + memory[instruction.address] = apply_mask(instruction.value, instruction.mask) + + sigma = 0 + for key in memory: + sigma += memory[key] + + return sigma diff --git a/14-dockingData/python/partTwo.py b/14-dockingData/python/partTwo.py new file mode 100644 index 0000000..c9d7adb --- /dev/null +++ b/14-dockingData/python/partTwo.py @@ -0,0 +1,51 @@ +from common import * +import copy + + +def get_memory_addresses(value: int, mask: str) -> List[int]: + + value = number_to_base(value, 2).zfill(len(mask)) + combi = [] + for (v, m) in zip(value, mask.lower()): + if m == "0": + combi.append(v) + elif m == "1": + combi.append("1") + else: + combi.append(m) + + mask = combi + + n = mask.count("x") + # generates sets of ones and zeroes in unique orders for n times + values = list(itertools.product(*[[0, 1]] * n)) + + xns = [] + + # Find all floating point iterations + for val_combo in values: + msk = copy.copy(mask) + val_counter = 0 + for i, char in enumerate(msk): + if char == "x": + msk[i] = str(val_combo[val_counter]) + val_counter += 1 + xns.append(int("".join(msk), 2)) + + return xns + + +def partTwo(instr: str) -> int: + input_instructions = parse(instr) + memory = {} + + for instruction in input_instructions: + addresses = get_memory_addresses(instruction.address, instruction.mask) + for address in addresses: + memory[address] = instruction.value + + sigma = 0 + for key in memory: + sigma += memory[key] + + return sigma