From 133c76a02a1d2b19ab8d7d23536076408746ebe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Tue, 21 Nov 2023 09:42:24 +0100 Subject: [PATCH 01/19] Initiate AoC 2019 in python --- 2019-python/.gitignore | 1 + 2019-python/README.md | 35 +++++++++++++ 2019-python/aoc.py | 95 ++++++++++++++++++++++++++++++++++ 2019-python/output/__init__.py | 42 +++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 2019-python/.gitignore create mode 100644 2019-python/README.md create mode 100644 2019-python/aoc.py create mode 100644 2019-python/output/__init__.py diff --git a/2019-python/.gitignore b/2019-python/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/2019-python/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/2019-python/README.md b/2019-python/README.md new file mode 100644 index 0000000..b2d6b04 --- /dev/null +++ b/2019-python/README.md @@ -0,0 +1,35 @@ +Advent of Code 2019 +=================== + +Solutions for #aoc2019 in Python 3 (3.11.5). + +Help scripts +------------ + +Display all solved puzzles: + + python aoc.py + +To bootstrap a new puzzle (creates `input/.txt` and `output/day_.py`): + + python aoc.py "" + +Manually copy the puzzle input from https://adventofcode.com and paste it in `input/.txt` +to start coding. + +Solve separate puzzle (replace `XX` with the puzzle number): + + python -m output.day_XX + +Solve separate puzzle using stdin (replace `XX` with the puzzle number): + + xclip -selection clipboard -o | python -m output.day_XX + cat tmpfile | python -m output.day_XX + +Execute separate puzzle on file save (replace `XX` with the puzzle number): + + ls output/*.py | entr -c -s 'xclip -selection clipboard -o | python -m output.day_XX' + ls output/*.py | entr -c -s 'cat tmpfile | python -m output.day_XX' + ls output/*.py | entr -c -r python -m output.day_XX + +(requires `entr` and `xclip`, Mac users can instead use `pbpaste`) diff --git a/2019-python/aoc.py b/2019-python/aoc.py new file mode 100644 index 0000000..9de0c9e --- /dev/null +++ b/2019-python/aoc.py @@ -0,0 +1,95 @@ +import sys + +year = 2019 + +try: + _, day_no, name = sys.argv +except ValueError: + day_no = None + name = None + +print( + f"\nAdvent of Code {year}" "\n###################" "\n\nby Anders Englöf Ytterström" +) + +if day_no and name: + padded_no = day_no.zfill(2) + print(f"\n- creating output/day_{padded_no}.py") + with open("output/day_{}.py".format(padded_no), "w") as s: + s.write( + f""" +from output import answer, puzzleinput + +n = {day_no} +title = "{name}" + + +@puzzleinput(n) +def parse_input(data): + return data + + +@answer(1, "Answer is {{}}") +def part_1(data): + return data + + +@answer(2, "Actually, answer is {{}}") +def part_2(data): + return data + + +if __name__ == "__main__": + # use dummy data + parsed = \"\"\" + replace me + \"\"\".strip() + + # uncomment to instead use stdin + # import fileinput + # parsed = "\\n".join(list(fileinput.input())) + + # uncomment to instead use content of input/{padded_no}.txt + # parsed = parse_input() + + part_1(parsed) + # part_2(parsed) +""".strip() + + "\n" + ) + print(f"- creating empty input/{day_no.zfill(2)}.txt") + with open("input/{}.txt".format(day_no.zfill(2)), "w") as i: + i.write("") + + print( + f""" +Done! start coding. + +Puzzle link: +https://adventofcode.com/{year}/day/{day_no} + +Puzzle input (copy and paste to input/{day_no.zfill(2)}.txt): +https://adventofcode.com/{year}/day/{day_no}/input + """ + ) + exit(0) + +from output import headline + +for i in [str(n).zfill(2) for n in range(1, 26)]: + try: + day = __import__( + "output.day_{}".format(i), + globals(), + locals(), + ["n", "title", "part_1", "part_2", "parse_input"], + 0, + ) + headline(day.n, day.title) + data = day.parse_input() + day.part_1(data, decorate=True) + day.part_2(data, decorate=True) + except IOError: + pass + except ImportError: + pass diff --git a/2019-python/output/__init__.py b/2019-python/output/__init__.py new file mode 100644 index 0000000..addf3ba --- /dev/null +++ b/2019-python/output/__init__.py @@ -0,0 +1,42 @@ +import functools + + +def puzzleinput(n, **kwargs): + filename = str(n).zfill(2) + trim_input = kwargs.get("trim_input", True) + filepath = f"./input/{filename}.txt" + + def decorator_pi(func): + @functools.wraps(func) + def wrapper_pi(*args, **kwargs): + with open(filepath, "r") as f: + data = f.read() + if trim_input: + return func(data.strip(), *args, **kwargs) + return func(data, *args, **kwargs) + + return wrapper_pi + + return decorator_pi + + +def answer(part_index, fmt_string): + def decorator_aoc(func): + @functools.wraps(func) + def wrapper_aoc(*args, **kwargs): + decorate = kwargs.get("decorate", False) + answer = func(*args) + if not decorate: + print(answer) + else: + formatted = fmt_string.format(answer) + print(f"[part {part_index}] {formatted}") + + return wrapper_aoc + + return decorator_aoc + + +def headline(n, title): + title = f"Day {n}: {title}" + print("\n".join(["", title, "".join("-" for _ in title), ""])) -- 2.45.3 From 85a67d26fa2a740abe952a3389abbb325e4bef54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Tue, 21 Nov 2023 09:43:48 +0100 Subject: [PATCH 02/19] Solve 2019:01 "The Tyranny of the Rocket Equation" --- 2019-python/output/day_01.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 2019-python/output/day_01.py diff --git a/2019-python/output/day_01.py b/2019-python/output/day_01.py new file mode 100644 index 0000000..64a49f7 --- /dev/null +++ b/2019-python/output/day_01.py @@ -0,0 +1,32 @@ +from output import answer, puzzleinput + +n = 1 +title = "The Tyranny of the Rocket Equation" + + +@puzzleinput(n) +def parse_input(data): + return list(map(int, data.split())) + + +@answer(1, "Total fuel requirements are {}") +def part_1(lines): + return sum(n // 3 - 2 for n in lines) + + +@answer(2, "Total fuel requirements are {} including fuel costs") +def part_2(lines): + s = 0 + for fuel in lines: + rem = fuel + while rem > 0: + cost = rem // 3 - 2 + s += max(0, cost) + rem = max(0, cost) + return s + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) -- 2.45.3 From e1d4741cfcb39fcf21cc2ae5d4d0f75958cd0a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Tue, 21 Nov 2023 12:01:10 +0100 Subject: [PATCH 03/19] Solve 2019:02 "1202 Program Alarm" --- 2019-python/output/day_02.py | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 2019-python/output/day_02.py diff --git a/2019-python/output/day_02.py b/2019-python/output/day_02.py new file mode 100644 index 0000000..29bff7f --- /dev/null +++ b/2019-python/output/day_02.py @@ -0,0 +1,56 @@ +from output import answer, puzzleinput +from collections import defaultdict + +n = 2 +title = "1202 Program Alarm" + + +@puzzleinput(n) +def parse_input(data): + return list(map(int, data.split(","))) + + +@answer(1, "Value of pos 0 is {} at halt signal") +def part_1(program): + state = dict(zip(range(len(program)), program)) + state[1] = 12 + state[2] = 2 + + for i in range(0, len(state), 4): + opcode, *args = list(state.values())[i : i + 4] + if opcode == 1: + a, b, p = args + state[p] = state[a] + state[b] + if opcode == 2: + a, b, p = args + state[p] = state[a] * state[b] + if opcode == 99: + break + return state[0] + + +@answer(2, "100 * noun + verb = {} for output 19690720") +def part_2(program, noun=76, verb=21): + state = dict(zip(range(len(program)), program)) + state[1] = noun + state[2] = verb + + for i in range(0, len(state), 4): + opcode, *args = list(state.values())[i : i + 4] + if opcode == 1: + a, b, p = args + state[p] = state[a] + state[b] + if opcode == 2: + a, b, p = args + state[p] = state[a] * state[b] + if opcode == 99: + break + if state[0] == 19690720: + return 100 * noun + verb + return state[0] + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed, 76, 21) # found manually by binary search -- 2.45.3 From 799216623e69680fb56ef9276091e26640953078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Tue, 21 Nov 2023 14:44:16 +0100 Subject: [PATCH 04/19] Solve 2019:03 "Crossed Wires" --- 2019-python/output/day_03.py | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 2019-python/output/day_03.py diff --git a/2019-python/output/day_03.py b/2019-python/output/day_03.py new file mode 100644 index 0000000..79967b1 --- /dev/null +++ b/2019-python/output/day_03.py @@ -0,0 +1,66 @@ +from collections import defaultdict +from output import answer, puzzleinput + +n = 3 +title = "Crossed Wires" + +directions = { + "U": (-1, 0), + "R": (0, 1), + "D": (1, 0), + "L": (0, -1), +} + + +@puzzleinput(n) +def parse_input(data): + return [line.split(",") for line in data.split()] + + +@answer( + 1, "As the crow flies, closest intersection Manhattan distance is {} units away" +) +def part_1(wires): + def follow(instructions): + seen = [] + pos = (0, 0) + for instruction in instructions: + urdl, *l = instruction + distance = int("".join(l)) + for _ in range(distance): + pos = (pos[0] + directions[urdl][0], pos[1] + directions[urdl][1]) + seen.append(pos) + return set(seen) + + wa = follow(wires[0]) + wb = follow(wires[1]) + + return min(sum(map(abs, i)) for i in wa & wb) + + +@answer(2, "By travel, closest intersection Manhattan distance is {} units away") +def part_2(wires): + seen = defaultdict(dict) + + def follow(instructions, i): + steps = 0 + pos = (0, 0) + for instruction in instructions: + urdl, *l = instruction + distance = int("".join(l)) + for _ in range(distance): + steps += 1 + pos = (pos[0] + directions[urdl][0], pos[1] + directions[urdl][1]) + if i not in seen[pos]: + seen[pos][i] = steps + + for i, wire in enumerate(wires): + follow(wire, i) + + return min(sum(v.values()) for v in seen.values() if len(v) > 1) + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) -- 2.45.3 From 298b97b66dfec5c6b192c52fb9546ddf994b84e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Tue, 21 Nov 2023 20:10:51 +0100 Subject: [PATCH 05/19] Solve 2019:04 "Secure Container" --- 2019-python/output/day_04.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 2019-python/output/day_04.py diff --git a/2019-python/output/day_04.py b/2019-python/output/day_04.py new file mode 100644 index 0000000..d0464b9 --- /dev/null +++ b/2019-python/output/day_04.py @@ -0,0 +1,36 @@ +from collections import Counter +from output import answer, puzzleinput + +n = 4 +title = "Secure Container" + + +@puzzleinput(n) +def parse_input(data): + return data.split("-") + + +@answer(1, "{} combinations of valid passwords") +def part_1(range_values): + a, b = range_values + + def valid(s): + return "".join(sorted(s)) == s and any(x == y for x, y in zip(s, s[1:])) + + return sum(valid(str(pw)) for pw in range(int(a), int(b) + 1)) + + +@answer(2, "{} combinations of valid passwords, including important detail") +def part_2(range_values): + a, b = range_values + + def valid(s): + return "".join(sorted(s)) == s and 2 in Counter(s).values() + + return sum(valid(str(pw)) for pw in range(int(a), int(b) + 1)) + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) -- 2.45.3 From 9eccd731b49bc10b69e3297b01a44cec7d9f1f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Thu, 23 Nov 2023 01:31:57 +0100 Subject: [PATCH 06/19] Solve 2019:05 "Sunny with a Chance of Asteroids" Refactored the intcode computer to live in a separate file, since it will come back more times. As a nice touch, output of aoc.py will print the version used of the intcode. As another nice touch, the int code has a CHANGELOG. --- 2019-python/output/day_02.py | 36 +----- 2019-python/output/day_05.py | 29 +++++ 2019-python/output/intcode_computer.py | 146 +++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 30 deletions(-) create mode 100644 2019-python/output/day_05.py create mode 100644 2019-python/output/intcode_computer.py diff --git a/2019-python/output/day_02.py b/2019-python/output/day_02.py index 29bff7f..6d10097 100644 --- a/2019-python/output/day_02.py +++ b/2019-python/output/day_02.py @@ -1,6 +1,8 @@ from output import answer, puzzleinput from collections import defaultdict +from output.intcode_computer import execute + n = 2 title = "1202 Program Alarm" @@ -10,41 +12,15 @@ def parse_input(data): return list(map(int, data.split(","))) -@answer(1, "Value of pos 0 is {} at halt signal") +@answer(1, "[intcode-0.1.0] Value of pos 0 is {} at halt signal") def part_1(program): - state = dict(zip(range(len(program)), program)) - state[1] = 12 - state[2] = 2 - - for i in range(0, len(state), 4): - opcode, *args = list(state.values())[i : i + 4] - if opcode == 1: - a, b, p = args - state[p] = state[a] + state[b] - if opcode == 2: - a, b, p = args - state[p] = state[a] * state[b] - if opcode == 99: - break + state, _ = execute(program, noun=12, verb=2) return state[0] -@answer(2, "100 * noun + verb = {} for output 19690720") +@answer(2, "[intcode-0.1.1] 100 * noun + verb = {} for output 19690720") def part_2(program, noun=76, verb=21): - state = dict(zip(range(len(program)), program)) - state[1] = noun - state[2] = verb - - for i in range(0, len(state), 4): - opcode, *args = list(state.values())[i : i + 4] - if opcode == 1: - a, b, p = args - state[p] = state[a] + state[b] - if opcode == 2: - a, b, p = args - state[p] = state[a] * state[b] - if opcode == 99: - break + state, _ = execute(program, noun, verb) if state[0] == 19690720: return 100 * noun + verb return state[0] diff --git a/2019-python/output/day_05.py b/2019-python/output/day_05.py new file mode 100644 index 0000000..83a7781 --- /dev/null +++ b/2019-python/output/day_05.py @@ -0,0 +1,29 @@ +from output import answer, puzzleinput + +from output.intcode_computer import execute + +n = 5 +title = "Sunny with a Chance of Asteroids" + + +@puzzleinput(n) +def parse_input(data): + return list(map(int, data.split(","))) + + +@answer(1, "[intcode-0.2.0] Program diagnostic code, ID 1: {}") +def part_1(program): + _, stdout = execute(program, stdin=1) + return max(stdout) + + +@answer(2, "[intcode-0.2.1] Program diagnostic code, ID 5: {}") +def part_2(program): + _, stdout = execute(program, stdin=5) + return stdout[0] + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) diff --git a/2019-python/output/intcode_computer.py b/2019-python/output/intcode_computer.py new file mode 100644 index 0000000..9473d5a --- /dev/null +++ b/2019-python/output/intcode_computer.py @@ -0,0 +1,146 @@ +from collections import defaultdict + + +def execute(program, noun=None, verb=None, stdin=0, debug=False): + """ + intcode computer, AoC 2019 + + Changelog + ========= + + 0.2.1 + ----- + + Patch release (day 5 part 2). + + - Add operation 5: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position + - Add operation 6: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position + - Add operation 7: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position + - Add operation 8: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position + + 0.2.0 + ----- + + Minor release (day 5 part 1). + + - Support immediate parameter mode + - Add stdin argument + - Make arguments optional: noun, verb + - Capture and return stdout + - Add operation 3: store stdin to parameter 1 position + - Add operation 4: output value at parameter 1 position to stdout + + 0.1.1 + ----- + + Patch release (day 2 part 2). + + - Remove initial modification 1=12, 2=2 + - Add noun argument, stored at pos 1 (default value: 12) + - Add verb argument, stored at pos 2 (default value: 2) + + 0.1.0 + ----- + + Initial version (day 2 part 1). + + - Support positional parameter mode + - Add operation 1: adds parameter 1 to parameter 2, store to parameter 3 position + - Add operation 2: multiply parameter 1 with parameter 2, store to parameter 3 position + """ + state = dict(zip(range(len(program)), program)) + if noun: + state[1] = noun + if verb: + state[2] = verb + n = 0 + c = 0 + stdout = [] + + while True: + instruction = state[n] + opcode = instruction % 100 + modes = str(instruction // 100 % 100).zfill(2) + if opcode == 1: + a = state[n + 1] + b = state[n + 2] + p = state[n + 3] + x = a if modes[1] == "1" else state[a] + y = b if modes[0] == "1" else state[b] + state[p] = x + y + n += 4 + if debug: + print(f"{n}:{opcode} | {x} + {y} to {p}") + if opcode == 2: + a = state[n + 1] + b = state[n + 2] + p = state[n + 3] + x = a if modes[1] == "1" else state[a] + y = b if modes[0] == "1" else state[b] + state[p] = x * y + n += 4 + if debug: + print(f"{n}:{opcode} | {x} * {y} to {p}") + if opcode == 3: + p = state[n + 1] + state[p] = stdin + n += 2 + if debug: + print(f"{n}:{opcode} | {i} to {p}") + if opcode == 4: + a = state[n + 1] + x = a if modes[1] == "1" else state[a] + n += 2 + stdout.append(x) + if debug: + print(f"{n}:{opcode} | {stdout}") + if opcode == 5: + a = state[n + 1] + b = state[n + 2] + x = a if modes[1] == "1" else state[a] + y = b if modes[0] == "1" else state[b] + if x != 0: + n = y + else: + n += 3 + if debug: + print(f"{n}:{opcode} | {n}") + if opcode == 6: + a = state[n + 1] + b = state[n + 2] + x = a if modes[1] == "1" else state[a] + y = b if modes[0] == "1" else state[b] + if x == 0: + n = y + else: + n += 3 + if debug: + print(f"{n}:{opcode} | {n}") + if opcode == 7: + a = state[n + 1] + b = state[n + 2] + p = state[n + 3] + x = a if modes[1] == "1" else state[a] + y = b if modes[0] == "1" else state[b] + state[p] = int(x < y) + n += 4 + if debug: + print(f"{n}:{opcode} | {x}") + if opcode == 8: + a = state[n + 1] + b = state[n + 2] + p = state[n + 3] + x = a if modes[1] == "1" else state[a] + y = b if modes[0] == "1" else state[b] + state[p] = int(x == y) + n += 4 + if debug: + print(f"{n}:{opcode} | {x}") + if opcode == 99: + break + c += 1 + if debug and c % 1000 == 0: + print(f"{c} instructions done, current pos: {n}") + # if c == 3: + # break + return state, stdout -- 2.45.3 From 5be719dcf971009b81cd2d19bf42d43a34045e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Thu, 23 Nov 2023 12:10:01 +0100 Subject: [PATCH 07/19] Solve 2019:06 "Universal Orbit Map" Tried to solve part 2 using BFS, but did not figure out a way to find shortest path. Fell back to a less elegant but more simple approach. --- 2019-python/output/day_06.py | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 2019-python/output/day_06.py diff --git a/2019-python/output/day_06.py b/2019-python/output/day_06.py new file mode 100644 index 0000000..3d4f201 --- /dev/null +++ b/2019-python/output/day_06.py @@ -0,0 +1,46 @@ +from collections import defaultdict, deque +from output import answer, puzzleinput + +n = 6 +title = "Universal Orbit Map" + + +@puzzleinput(n) +def parse_input(data): + heritage = defaultdict(str) + for parent, child in [line.split(")") for line in data.split()]: + heritage[child] = parent + return heritage + + +@answer(1, "{} direct and indirect orbits") +def part_1(heritage): + return sum(len(ancestry(heritage, v)) for v in heritage.keys()) + + +@answer(2, "Orbit transfers needed for you to share orbit with Santa: {}") +def part_2(heritage): + a = ancestry(heritage, "YOU") + b = ancestry(heritage, "SAN") + shared = len(set(a) & set(b)) + return sum( + [ + len(a) - shared, + len(b) - shared, + ] + ) + + +def ancestry(parents, child): + k = child + lineage = [] + while k in parents: + lineage.append(parents[k]) + k = parents[k] + return lineage[::-1] + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) -- 2.45.3 From 4623fdac95be265d056797d50ae252e1ae3985bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Thu, 23 Nov 2023 14:49:48 +0100 Subject: [PATCH 08/19] Solve 2019:08 "Space Image Format" --- 2019-python/output/__init__.py | 4 +++- 2019-python/output/day_08.py | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 2019-python/output/day_08.py diff --git a/2019-python/output/__init__.py b/2019-python/output/__init__.py index addf3ba..7877cc4 100644 --- a/2019-python/output/__init__.py +++ b/2019-python/output/__init__.py @@ -25,7 +25,9 @@ def answer(part_index, fmt_string): @functools.wraps(func) def wrapper_aoc(*args, **kwargs): decorate = kwargs.get("decorate", False) - answer = func(*args) + if decorate: + del kwargs["decorate"] + answer = func(*args, **kwargs) if not decorate: print(answer) else: diff --git a/2019-python/output/day_08.py b/2019-python/output/day_08.py new file mode 100644 index 0000000..93e495c --- /dev/null +++ b/2019-python/output/day_08.py @@ -0,0 +1,38 @@ +from collections import Counter +from textwrap import wrap +from output import answer, puzzleinput + +n = 8 +title = "Space Image Format" + + +@puzzleinput(n) +def parse_input(data): + return data + + +@answer(1, "the product of all 1s and 2s in the layer with fewest 0s is {}") +def part_1(data): + layers = sorted(map(Counter, wrap(data, 25 * 6)), key=lambda c: c["0"]) + a = layers[0]["1"] + b = layers[0]["2"] + return a * b + + +@answer(2, "The message is CYUAH, the decoded image looks like this:\n\n{}") +def part_2(data, width=25, height=6): + layers = wrap(data, width * height) + l = len(layers) + pixels = zip(*layers) + lit = map( + lambda s: s.replace("0", ".").replace("1", "#"), + map(lambda p: next(filter(lambda x: x != "2", p)), pixels), + ) + matrix = "\n".join(wrap("".join(lit), width)) + return matrix + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) -- 2.45.3 From c51638fac1a6eceaa6f3a8ec1e1df69a2e7acaa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Thu, 23 Nov 2023 17:08:13 +0100 Subject: [PATCH 09/19] Solve 2019:07 "Amplication Circuit" Breaking change for intcode 0.3.0, revisited earlier use of the computer to update code. --- 2019-python/output/day_02.py | 8 +- 2019-python/output/day_05.py | 8 +- 2019-python/output/day_07.py | 65 +++++++++++++ 2019-python/output/intcode_computer.py | 122 +++++++++++++++++-------- 4 files changed, 158 insertions(+), 45 deletions(-) create mode 100644 2019-python/output/day_07.py diff --git a/2019-python/output/day_02.py b/2019-python/output/day_02.py index 6d10097..e9e4465 100644 --- a/2019-python/output/day_02.py +++ b/2019-python/output/day_02.py @@ -1,7 +1,7 @@ from output import answer, puzzleinput from collections import defaultdict -from output.intcode_computer import execute +from output.intcode_computer import execute, parse n = 2 title = "1202 Program Alarm" @@ -9,18 +9,18 @@ title = "1202 Program Alarm" @puzzleinput(n) def parse_input(data): - return list(map(int, data.split(","))) + return parse(data) @answer(1, "[intcode-0.1.0] Value of pos 0 is {} at halt signal") def part_1(program): - state, _ = execute(program, noun=12, verb=2) + _code, state, *_unused = execute(program, noun=12, verb=2) return state[0] @answer(2, "[intcode-0.1.1] 100 * noun + verb = {} for output 19690720") def part_2(program, noun=76, verb=21): - state, _ = execute(program, noun, verb) + _code, state, *_unused = execute(program, noun, verb) if state[0] == 19690720: return 100 * noun + verb return state[0] diff --git a/2019-python/output/day_05.py b/2019-python/output/day_05.py index 83a7781..2718781 100644 --- a/2019-python/output/day_05.py +++ b/2019-python/output/day_05.py @@ -1,6 +1,6 @@ from output import answer, puzzleinput -from output.intcode_computer import execute +from output.intcode_computer import execute, parse n = 5 title = "Sunny with a Chance of Asteroids" @@ -8,18 +8,18 @@ title = "Sunny with a Chance of Asteroids" @puzzleinput(n) def parse_input(data): - return list(map(int, data.split(","))) + return parse(data) @answer(1, "[intcode-0.2.0] Program diagnostic code, ID 1: {}") def part_1(program): - _, stdout = execute(program, stdin=1) + _code, _state, _cursorpos, stdout = execute(program, stdin=1) return max(stdout) @answer(2, "[intcode-0.2.1] Program diagnostic code, ID 5: {}") def part_2(program): - _, stdout = execute(program, stdin=5) + _code, _state, _cursorpos, stdout = execute(program, stdin=5) return stdout[0] diff --git a/2019-python/output/day_07.py b/2019-python/output/day_07.py new file mode 100644 index 0000000..69f8341 --- /dev/null +++ b/2019-python/output/day_07.py @@ -0,0 +1,65 @@ +from collections import defaultdict +from itertools import permutations + +from output import answer, puzzleinput +from output.intcode_computer import execute, parse + +n = 7 +title = "Amplification Circuit" + + +@puzzleinput(n) +def parse_input(data): + return parse(data) + + +@answer( + 1, + "[intcode 0.3.0] Given the phase settings [0, 3, 1, 2, 4], the highest achievable signal to the thruster is {}", +) +def part_1(program): + thruster_signals = [] + for settings in map(list, permutations(range(5))): + o = 0 + for ps in settings: + _code, _state, _n, so = execute(program, stdin=[ps, o]) + o = so.pop(0) + thruster_signals.append(o) + return max(thruster_signals) + + +@answer( + 2, + "[intcode 0.3.0] Given the phase settings [7, 8, 5, 9, 6] and creating feedback loop, the highest achievable signal to the thruster is {}", +) +def part_2(program): + thruster_signals = [] + for settings in map(list, permutations(range(5, 10))): + o = [0] + finished = set() + paused = defaultdict(tuple) + while len(finished) < 5: + for amp, ps in enumerate(settings): + if paused[amp]: + program, resume_at = paused[amp] + del paused[amp] + code, state, n, so = execute(program, stdin=o, n=resume_at) + else: + code, state, n, so = execute(program, stdin=[ps, *o]) + if code == 3: + paused[amp] = ( + list(state.values()), + n, + ) + o = so + if code == 99: + finished.add(amp) + o = so + thruster_signals.append(o[-1]) + return max(thruster_signals) + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) diff --git a/2019-python/output/intcode_computer.py b/2019-python/output/intcode_computer.py index 9473d5a..5638e94 100644 --- a/2019-python/output/intcode_computer.py +++ b/2019-python/output/intcode_computer.py @@ -1,61 +1,99 @@ from collections import defaultdict -def execute(program, noun=None, verb=None, stdin=0, debug=False): - """ - intcode computer, AoC 2019 +""" +intcode computer, AoC 2019 - Changelog - ========= +Changelog +========= - 0.2.1 - ----- +0.3.0 +----- - Patch release (day 5 part 2). +Minor release (day 7 part 1-2). - - Add operation 5: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position - - Add operation 6: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position - - Add operation 7: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position - - Add operation 8: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position +BREAKING CHANGE: execute() now returns 4 values. +- now: exit code, state at halt, instruction position at halt, and captured stdout. +- before: final state, and captured stdout - 0.2.0 - ----- +Changes: - Minor release (day 5 part 1). +- Add support for a sequence of stdins +- Add interactive param to ask for manual (interactive) input on input opcode +- Add verbose param to show more output in interactive input mode +- Will now halt with code 3 (input) when input is required, stdin is empty and interactive input mode is not enabled - - Support immediate parameter mode - - Add stdin argument - - Make arguments optional: noun, verb - - Capture and return stdout - - Add operation 3: store stdin to parameter 1 position - - Add operation 4: output value at parameter 1 position to stdout +0.2.1 +----- - 0.1.1 - ----- +Patch release (day 5 part 2). - Patch release (day 2 part 2). +- Add operation 5: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position +- Add operation 6: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position +- Add operation 7: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position +- Add operation 8: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position - - Remove initial modification 1=12, 2=2 - - Add noun argument, stored at pos 1 (default value: 12) - - Add verb argument, stored at pos 2 (default value: 2) +0.2.0 +----- - 0.1.0 - ----- +Minor release (day 5 part 1). - Initial version (day 2 part 1). +- Support immediate parameter mode +- Add stdin argument +- Make arguments optional: noun, verb +- Capture and return stdout +- Add operation 3: store stdin to parameter 1 position +- Add operation 4: output value at parameter 1 position to stdout - - Support positional parameter mode - - Add operation 1: adds parameter 1 to parameter 2, store to parameter 3 position - - Add operation 2: multiply parameter 1 with parameter 2, store to parameter 3 position - """ +0.1.1 +----- + +Patch release (day 2 part 2). + +- Remove initial modification 1=12, 2=2 +- Add noun argument, stored at pos 1 (default value: 12) +- Add verb argument, stored at pos 2 (default value: 2) + +0.1.0 +----- + +Initial version (day 2 part 1). + +- Support positional parameter mode +- Add operation 1: adds parameter 1 to parameter 2, store to parameter 3 position +- Add operation 2: multiply parameter 1 with parameter 2, store to parameter 3 position +""" +v = "0.2.3" + + +def parse(data): + return list(map(int, data.split(","))) + + +def execute( + program, + noun=None, + verb=None, + stdin=[], + debug=False, + interactive=False, + verbose=False, + n=0, +): + if verbose: + title = f"intcode computer, version {v}" + print("".join("=" for _ in title)) + print(title) + print("".join("=" for _ in title)) state = dict(zip(range(len(program)), program)) if noun: state[1] = noun if verb: state[2] = verb - n = 0 c = 0 stdout = [] + if not isinstance(stdin, list): + stdin = [stdin] while True: instruction = state[n] @@ -83,7 +121,13 @@ def execute(program, noun=None, verb=None, stdin=0, debug=False): print(f"{n}:{opcode} | {x} * {y} to {p}") if opcode == 3: p = state[n + 1] - state[p] = stdin + if stdin: + state[p] = stdin.pop(0) + else: + if interactive: + state[p] = int(input("> ")) + else: + return 3, state, n, stdout n += 2 if debug: print(f"{n}:{opcode} | {i} to {p}") @@ -92,6 +136,8 @@ def execute(program, noun=None, verb=None, stdin=0, debug=False): x = a if modes[1] == "1" else state[a] n += 2 stdout.append(x) + if verbose: + print(x) if debug: print(f"{n}:{opcode} | {stdout}") if opcode == 5: @@ -143,4 +189,6 @@ def execute(program, noun=None, verb=None, stdin=0, debug=False): print(f"{c} instructions done, current pos: {n}") # if c == 3: # break - return state, stdout + if verbose: + title = f"intcode computer received SIGTERM" + return 99, state, n, stdout -- 2.45.3 From d9142068ea158cf125cffca4fbfb6f647397bdfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Fri, 24 Nov 2023 17:26:06 +0100 Subject: [PATCH 10/19] Solve 2019:09 "Sensor Boost" This was a hard one. I would not have made it without r/adventofcode where all assertions in __main__ were fetched. For values it was straight forward enough, but for some reason it did not work for positioning. I still do not understand how one is supposed to chew the WALL OF TEXT each day of 2019 and figure out these stuffs on your own. It took 10-12 hours to solve part 1. AoC day 9 is no joke. Links that helped me: - https://www.reddit.com/r/adventofcode/comments/e8aw9j/2019_day_9_part_1_how_to_fix_203_error/ - https://www.reddit.com/r/adventofcode/comments/jr9xrn/comment/gbwr85k/ --- 2019-python/aoc.py | 6 ++ 2019-python/output/day_09.py | 79 ++++++++++++++ 2019-python/output/intcode_computer.py | 138 ++++++++++++++++++------- 3 files changed, 184 insertions(+), 39 deletions(-) create mode 100644 2019-python/output/day_09.py diff --git a/2019-python/aoc.py b/2019-python/aoc.py index 9de0c9e..d266e50 100644 --- a/2019-python/aoc.py +++ b/2019-python/aoc.py @@ -76,6 +76,7 @@ https://adventofcode.com/{year}/day/{day_no}/input from output import headline +stars = 0 for i in [str(n).zfill(2) for n in range(1, 26)]: try: day = __import__( @@ -88,8 +89,13 @@ for i in [str(n).zfill(2) for n in range(1, 26)]: headline(day.n, day.title) data = day.parse_input() day.part_1(data, decorate=True) + stars += 1 day.part_2(data, decorate=True) + stars += 1 except IOError: pass except ImportError: pass +print(f"\nStars: {stars}") +print("".join("*" if n <= stars else "•" for n in range(50))) +print("") diff --git a/2019-python/output/day_09.py b/2019-python/output/day_09.py new file mode 100644 index 0000000..f564672 --- /dev/null +++ b/2019-python/output/day_09.py @@ -0,0 +1,79 @@ +from output import answer, puzzleinput +from output.intcode_computer import execute, parse + +n = 9 +title = "Sensor Boost" + + +@puzzleinput(n) +def parse_input(data): + return parse(data) + + +@answer(1, "[intcode 0.3.1] BOOST keycode: {}") +def part_1(program): + _c, _s, _n, outputs = execute(program, stdin=[1]) + return outputs.pop(0) + + +@answer(2, "[intcode 0.3.1] Distress signal coordinates: {}") +def part_2(program): + _c, _s, _n, outputs = execute(program, stdin=[2]) + return outputs.pop(0) + + +if __name__ == "__main__": + assert execute( + [ + 109, + 1, + 204, + -1, + 1001, + 100, + 1, + 100, + 1008, + 100, + 16, + 101, + 1006, + 101, + 0, + 99, + ] + )[3] == [ + 109, + 1, + 204, + -1, + 1001, + 100, + 1, + 100, + 1008, + 100, + 16, + 101, + 1006, + 101, + 0, + 99, + ] + assert len(str(execute([1102, 34915192, 34915192, 7, 4, 7, 99, 0])[3][0])) == 16 + assert 1125899906842624 in execute([104, 1125899906842624, 99])[3] + assert execute([109, -1, 4, 1, 99])[3][0] == -1 + assert execute([109, -1, 104, 1, 99])[3][0] == 1 + assert execute([109, -1, 204, 1, 99])[3][0] == 109 + assert execute([109, 1, 9, 2, 204, -6, 99])[3][0] == 204 + assert execute([109, 1, 109, 9, 204, -6, 99])[3][0] == 204 + assert execute([109, 1, 209, -1, 204, -106, 99])[3][0] == 204 + assert execute([109, 1, 3, 3, 204, 2, 99], stdin=[666])[3][0] == 666 + assert execute([109, 1, 203, 2, 204, 2, 99], stdin=[666])[3][0] == 666 + assert execute([109, 6, 21001, 9, 25, 1, 104, 0, 99, 49])[3][0] == 74 + + parsed = parse_input() + assert execute(parsed, stdin=[1])[3][0] == 2351176124 + + part_1(parsed) + part_2(parsed) diff --git a/2019-python/output/intcode_computer.py b/2019-python/output/intcode_computer.py index 5638e94..95630cf 100644 --- a/2019-python/output/intcode_computer.py +++ b/2019-python/output/intcode_computer.py @@ -7,13 +7,21 @@ intcode computer, AoC 2019 Changelog ========= +0.3.1 +----- + +Patch release (day 7 part 1-2). + +- Supports relative parameter mode + 0.3.0 ----- Minor release (day 7 part 1-2). BREAKING CHANGE: execute() now returns 4 values. -- now: exit code, state at halt, instruction position at halt, and captured stdout. + +- now: exit code, state at halt, instruction position at halt, and captured stdout - before: final state, and captured stdout Changes: @@ -63,7 +71,7 @@ Initial version (day 2 part 1). - Add operation 1: adds parameter 1 to parameter 2, store to parameter 3 position - Add operation 2: multiply parameter 1 with parameter 2, store to parameter 3 position """ -v = "0.2.3" +__version__ = "0.3.1" def parse(data): @@ -85,77 +93,118 @@ def execute( print("".join("=" for _ in title)) print(title) print("".join("=" for _ in title)) - state = dict(zip(range(len(program)), program)) + state = defaultdict(int) + for k, v in zip(range(len(program)), program): + state[k] = v if noun: state[1] = noun if verb: state[2] = verb c = 0 + rb = 0 stdout = [] if not isinstance(stdin, list): stdin = [stdin] + def halt(code): + return code, state, n, stdout + + def values(modes, *parameters): + for i, v in enumerate(parameters): + if modes[i] == "0" and v < 0: + print("================ ERROR =================") + print("Negative index provided to position mode") + if modes[i] == "2" and rb + v < 0: + print("================ ERROR =================") + print("Negative index provided to relative mode") + + def value(i, v): + if modes[i] == "1": + return v + if modes[i] == "2": + return state[v + rb] + return state[v] + + if len(parameters) > 1: + return [value(i, v) for i, v in enumerate(parameters)] + return value(0, parameters[0]) + while True: instruction = state[n] + # if instruction > 200 and instruction < 1000: + # print("") + # spn = 2 if instruction % 100 % 3 == 0 else 4 + # print(list(state.values())[n : n + spn]) opcode = instruction % 100 - modes = str(instruction // 100 % 100).zfill(2) + modes = str(instruction // 100).zfill(3)[::-1] if opcode == 1: a = state[n + 1] b = state[n + 2] - p = state[n + 3] - x = a if modes[1] == "1" else state[a] - y = b if modes[0] == "1" else state[b] + c = n + 3 + x, y = values(modes, a, b) + p = state[c] + if modes[2] == "2": + p += rb + if debug: + print(f"@{str(n).zfill(4)} {opcode}_ADDITION | {x} + {y} to {p})") state[p] = x + y n += 4 - if debug: - print(f"{n}:{opcode} | {x} + {y} to {p}") if opcode == 2: a = state[n + 1] b = state[n + 2] - p = state[n + 3] - x = a if modes[1] == "1" else state[a] - y = b if modes[0] == "1" else state[b] + c = n + 3 + x, y = values(modes, a, b) + p = state[c] + if modes[2] == "2": + p += rb + if debug: + print(f"@{str(n).zfill(4)} {opcode}_MULTIPLY | {x} * {y} to {p}") state[p] = x * y n += 4 - if debug: - print(f"{n}:{opcode} | {x} * {y} to {p}") if opcode == 3: - p = state[n + 1] + a = n + 1 + p = state[a] + if modes[0] == "2": + p += rb + if debug: + print( + f"@{str(n).zfill(4)} {opcode}_INPUT | target={p}, queued={stdin}, interactive={interactive}" + ) if stdin: state[p] = stdin.pop(0) else: if interactive: state[p] = int(input("> ")) else: - return 3, state, n, stdout + if debug: + print(f"@{str(n).zfill(4)} [suspended, awaiting input]") + return halt(3) n += 2 - if debug: - print(f"{n}:{opcode} | {i} to {p}") if opcode == 4: a = state[n + 1] - x = a if modes[1] == "1" else state[a] - n += 2 + x = values(modes, a) stdout.append(x) if verbose: print(x) if debug: - print(f"{n}:{opcode} | {stdout}") + print(f"@{str(n).zfill(4)} {opcode}_OUTPUT | echo {x}") + n += 2 if opcode == 5: a = state[n + 1] b = state[n + 2] - x = a if modes[1] == "1" else state[a] - y = b if modes[0] == "1" else state[b] + x, y = values(modes, a, b) if x != 0: + if debug: + print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-1 | {x} != 0, n={y}") n = y else: + if debug: + print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-1 | {x} != 0, ignoring") n += 3 - if debug: - print(f"{n}:{opcode} | {n}") if opcode == 6: a = state[n + 1] b = state[n + 2] - x = a if modes[1] == "1" else state[a] - y = b if modes[0] == "1" else state[b] + x, y = values(modes, a, b) if x == 0: n = y else: @@ -165,30 +214,41 @@ def execute( if opcode == 7: a = state[n + 1] b = state[n + 2] - p = state[n + 3] - x = a if modes[1] == "1" else state[a] - y = b if modes[0] == "1" else state[b] + c = n + 3 + x, y = values(modes, a, b) + p = state[c] + if modes[2] == "2": + p += rb + if debug: + print(f"@{str(n).zfill(4)} {opcode}_LESSTHAN | {x} < {y} to {p}") state[p] = int(x < y) n += 4 - if debug: - print(f"{n}:{opcode} | {x}") if opcode == 8: a = state[n + 1] b = state[n + 2] - p = state[n + 3] - x = a if modes[1] == "1" else state[a] - y = b if modes[0] == "1" else state[b] + c = n + 3 + x, y = values(modes, a, b) + p = state[c] + if modes[2] == "2": + p += rb + if debug: + print(f"@{str(n).zfill(4)} {opcode}_EQUALS | {x} == {y} to {p}") state[p] = int(x == y) n += 4 + if opcode == 9: + a = state[n + 1] + x = values(modes, a) if debug: - print(f"{n}:{opcode} | {x}") + print(f"@{str(n).zfill(4)} {opcode}_RELBASE | {rb} + {x}") + rb += x + n += 2 if opcode == 99: break c += 1 if debug and c % 1000 == 0: print(f"{c} instructions done, current pos: {n}") - # if c == 3: - # break + if c == 33: + break if verbose: title = f"intcode computer received SIGTERM" - return 99, state, n, stdout + return halt(99) -- 2.45.3 From 262ad34c517fcace86515b5c96d02780ee9203b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Mon, 27 Nov 2023 16:33:09 +0100 Subject: [PATCH 11/19] Solve 2019:10 "Monitoring Station". --- 2019-python/aoc.py | 3 +- 2019-python/output/day_07.py | 4 +- 2019-python/output/day_08.py | 2 +- 2019-python/output/day_10.py | 83 ++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 2019-python/output/day_10.py diff --git a/2019-python/aoc.py b/2019-python/aoc.py index d266e50..07a42b2 100644 --- a/2019-python/aoc.py +++ b/2019-python/aoc.py @@ -3,7 +3,7 @@ import sys year = 2019 try: - _, day_no, name = sys.argv + _, day_no, *name = sys.argv except ValueError: day_no = None name = None @@ -13,6 +13,7 @@ print( ) if day_no and name: + name = " ".join(name) padded_no = day_no.zfill(2) print(f"\n- creating output/day_{padded_no}.py") with open("output/day_{}.py".format(padded_no), "w") as s: diff --git a/2019-python/output/day_07.py b/2019-python/output/day_07.py index 69f8341..b0e63df 100644 --- a/2019-python/output/day_07.py +++ b/2019-python/output/day_07.py @@ -15,7 +15,7 @@ def parse_input(data): @answer( 1, - "[intcode 0.3.0] Given the phase settings [0, 3, 1, 2, 4], the highest achievable signal to the thruster is {}", + "[intcode 0.3.0] The highest achievable signal to the thruster is {}", ) def part_1(program): thruster_signals = [] @@ -30,7 +30,7 @@ def part_1(program): @answer( 2, - "[intcode 0.3.0] Given the phase settings [7, 8, 5, 9, 6] and creating feedback loop, the highest achievable signal to the thruster is {}", + "[intcode 0.3.0] By creating a feedback loop, the highest achievable signal to the thruster is {}", ) def part_2(program): thruster_signals = [] diff --git a/2019-python/output/day_08.py b/2019-python/output/day_08.py index 93e495c..3abd526 100644 --- a/2019-python/output/day_08.py +++ b/2019-python/output/day_08.py @@ -11,7 +11,7 @@ def parse_input(data): return data -@answer(1, "the product of all 1s and 2s in the layer with fewest 0s is {}") +@answer(1, "The product of all 1s and 2s in the layer with fewest 0s is {}") def part_1(data): layers = sorted(map(Counter, wrap(data, 25 * 6)), key=lambda c: c["0"]) a = layers[0]["1"] diff --git a/2019-python/output/day_10.py b/2019-python/output/day_10.py new file mode 100644 index 0000000..ffaaa37 --- /dev/null +++ b/2019-python/output/day_10.py @@ -0,0 +1,83 @@ +from collections import OrderedDict, defaultdict, deque +from math import atan2 +from output import answer, puzzleinput + +n = 10 +title = "Monitoring Station" + + +@puzzleinput(n) +def parse_input(data): + return data.strip().split() + + +@answer(1, "The monitor station will see {} asteroids at best") +def part_1(matrix): + _pos, visible = _map_visible_asteroids(matrix) + return len(set(dict(visible).values())) + + +@answer( + 2, + "The asteroid at y=3 x=17 (checksum {}) will be the 200th lazer vapored asteroid, making some elf happy", +) +def part_2(matrix): + pos, visible = _map_visible_asteroids(matrix) + targets_upper = defaultdict(list) + targets_lower = defaultdict(list) + targets = dict() + + for xy, angle in visible: + if angle < 0: + targets_lower[angle].append(xy) + else: + targets_upper[angle].append(xy) + + for k, v in OrderedDict( + sorted(targets_upper.items(), key=lambda x: x[0], reverse=True) + + sorted(targets_lower.items(), key=lambda x: x[0], reverse=True) + ).items(): + targets[k] = deque( + sorted( + v, + key=lambda xy: sum(abs(pos[i] - xy[i]) for i in range(2)), + ) + ) + + vapored = 0 + x = 0 + y = 0 + while vapored < 200: + popped = False + for tk in targets.keys(): + if targets[tk]: + x, y = targets[tk].pop() + vapored += 1 + popped = True + if vapored == 200: + break + if not popped: + break + return x * 100 + y + + +def _map_visible_asteroids(matrix): + asteroids = [] + visible = defaultdict(int) + + for y in range(len(matrix)): + for x in range(len(matrix[0])): + if matrix[y][x] == "#": + asteroids.append((x, y)) + for a, b in asteroids: + visible[(a, b)] = [ + ((x, y), atan2(x - a, y - b)) for x, y in asteroids if (a, b) != (x, y) + ] + + return max(visible.items(), key=lambda x: len(set(dict(x[1]).values()))) + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) -- 2.45.3 From f98545ae5abae5364ff36af1dd70c993ce912961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Tue, 28 Nov 2023 14:01:49 +0100 Subject: [PATCH 12/19] Solve 2019:11 "Space Police" Bugs discovered in intcode: - Relative base was reset on await input suspend. - No handling for incorrect intcodes. - State was sometimes corrupted when resumed after suspend. Fixed and Patched in 0.3.2 --- 2019-python/output/__init__.py | 1 + 2019-python/output/day_05.py | 4 +- 2019-python/output/day_07.py | 6 +- 2019-python/output/day_09.py | 30 +++---- 2019-python/output/day_11.py | 82 +++++++++++++++++ 2019-python/output/intcode_computer.py | 119 ++++++++++++++----------- 6 files changed, 168 insertions(+), 74 deletions(-) create mode 100644 2019-python/output/day_11.py diff --git a/2019-python/output/__init__.py b/2019-python/output/__init__.py index 7877cc4..4483fa4 100644 --- a/2019-python/output/__init__.py +++ b/2019-python/output/__init__.py @@ -33,6 +33,7 @@ def answer(part_index, fmt_string): else: formatted = fmt_string.format(answer) print(f"[part {part_index}] {formatted}") + return answer return wrapper_aoc diff --git a/2019-python/output/day_05.py b/2019-python/output/day_05.py index 2718781..179e8c3 100644 --- a/2019-python/output/day_05.py +++ b/2019-python/output/day_05.py @@ -13,13 +13,13 @@ def parse_input(data): @answer(1, "[intcode-0.2.0] Program diagnostic code, ID 1: {}") def part_1(program): - _code, _state, _cursorpos, stdout = execute(program, stdin=1) + _code, _state, _cursorpos, rb, stdout = execute(program, stdin=[1]) return max(stdout) @answer(2, "[intcode-0.2.1] Program diagnostic code, ID 5: {}") def part_2(program): - _code, _state, _cursorpos, stdout = execute(program, stdin=5) + _code, _state, _cursorpos, rb, stdout = execute(program, stdin=[5]) return stdout[0] diff --git a/2019-python/output/day_07.py b/2019-python/output/day_07.py index b0e63df..2e90e5e 100644 --- a/2019-python/output/day_07.py +++ b/2019-python/output/day_07.py @@ -22,7 +22,7 @@ def part_1(program): for settings in map(list, permutations(range(5))): o = 0 for ps in settings: - _code, _state, _n, so = execute(program, stdin=[ps, o]) + _code, _state, _n, _rb, so = execute(program, stdin=[ps, o]) o = so.pop(0) thruster_signals.append(o) return max(thruster_signals) @@ -43,9 +43,9 @@ def part_2(program): if paused[amp]: program, resume_at = paused[amp] del paused[amp] - code, state, n, so = execute(program, stdin=o, n=resume_at) + code, state, n, _rb, so = execute(program, stdin=o, n=resume_at) else: - code, state, n, so = execute(program, stdin=[ps, *o]) + code, state, n, _rb, so = execute(program, stdin=[ps, *o]) if code == 3: paused[amp] = ( list(state.values()), diff --git a/2019-python/output/day_09.py b/2019-python/output/day_09.py index f564672..5d118dc 100644 --- a/2019-python/output/day_09.py +++ b/2019-python/output/day_09.py @@ -12,13 +12,13 @@ def parse_input(data): @answer(1, "[intcode 0.3.1] BOOST keycode: {}") def part_1(program): - _c, _s, _n, outputs = execute(program, stdin=[1]) + _c, _s, _n, _rb, outputs = execute(program, stdin=[1]) return outputs.pop(0) @answer(2, "[intcode 0.3.1] Distress signal coordinates: {}") def part_2(program): - _c, _s, _n, outputs = execute(program, stdin=[2]) + _c, _s, _n, _rb, outputs = execute(program, stdin=[2]) return outputs.pop(0) @@ -42,7 +42,7 @@ if __name__ == "__main__": 0, 99, ] - )[3] == [ + )[4] == [ 109, 1, 204, @@ -60,20 +60,20 @@ if __name__ == "__main__": 0, 99, ] - assert len(str(execute([1102, 34915192, 34915192, 7, 4, 7, 99, 0])[3][0])) == 16 - assert 1125899906842624 in execute([104, 1125899906842624, 99])[3] - assert execute([109, -1, 4, 1, 99])[3][0] == -1 - assert execute([109, -1, 104, 1, 99])[3][0] == 1 - assert execute([109, -1, 204, 1, 99])[3][0] == 109 - assert execute([109, 1, 9, 2, 204, -6, 99])[3][0] == 204 - assert execute([109, 1, 109, 9, 204, -6, 99])[3][0] == 204 - assert execute([109, 1, 209, -1, 204, -106, 99])[3][0] == 204 - assert execute([109, 1, 3, 3, 204, 2, 99], stdin=[666])[3][0] == 666 - assert execute([109, 1, 203, 2, 204, 2, 99], stdin=[666])[3][0] == 666 - assert execute([109, 6, 21001, 9, 25, 1, 104, 0, 99, 49])[3][0] == 74 + assert len(str(execute([1102, 34915192, 34915192, 7, 4, 7, 99, 0])[4][0])) == 16 + assert 1125899906842624 in execute([104, 1125899906842624, 99])[4] + assert execute([109, -1, 4, 1, 99])[4][0] == -1 + assert execute([109, -1, 104, 1, 99])[4][0] == 1 + assert execute([109, -1, 204, 1, 99])[4][0] == 109 + assert execute([109, 1, 9, 2, 204, -6, 99])[4][0] == 204 + assert execute([109, 1, 109, 9, 204, -6, 99])[4][0] == 204 + assert execute([109, 1, 209, -1, 204, -106, 99])[4][0] == 204 + assert execute([109, 1, 3, 3, 204, 2, 99], stdin=[666])[4][0] == 666 + assert execute([109, 1, 203, 2, 204, 2, 99], stdin=[666])[4][0] == 666 + assert execute([109, 6, 21001, 9, 25, 1, 104, 0, 99, 49])[4][0] == 74 parsed = parse_input() - assert execute(parsed, stdin=[1])[3][0] == 2351176124 + assert execute(parsed, stdin=[1])[4][0] == 2351176124 part_1(parsed) part_2(parsed) diff --git a/2019-python/output/day_11.py b/2019-python/output/day_11.py new file mode 100644 index 0000000..6f4d790 --- /dev/null +++ b/2019-python/output/day_11.py @@ -0,0 +1,82 @@ +from collections import defaultdict +from output.intcode_computer import execute, parse +from output import answer, puzzleinput + +n = 11 +title = "Space Police" + + +@puzzleinput(n) +def parse_input(data): + return parse(data) + + +DIRS = [ + (-1, 0), + (0, -1), + (1, 0), + (0, 1), +] + +COLORS = [".", "#"] + +CL = ["black", "white"] +DL = ["UP", "LEFT", "BOTTOM", "RIGHT"] +TL = ["RIGHT", "LEFT"] + + +@answer(1, "[intcode 0.3.2] Robot paints {} panes at least once") +def part_1(program): + path, pos, d = _paint(program) + return len(path) + + +@answer( + 2, + '[intcode 0.3.2] The hull has registration identifier "JZPJRAGJ" freshly painted, see below: \n\n{}', +) +def part_2(program): + path, pos, d = _paint(program, 1) + return _inspect(path.copy(), pos, d) + + +def _paint(program, initial=0): + pos = (0, 0) + d = 0 + path = defaultdict(int) + path[pos] = initial + n = 0 + rb = 0 + code = 0 + while True: + code, program, n, rb, outputs = execute(program, n=n, rb=rb, stdin=[path[pos]]) + if code == 99: + break + if outputs: + paint, turn_to = outputs + path[pos] = paint + d = (d - 1 if turn_to == 1 else d + 1) % 4 + pos = (pos[0] + DIRS[d][0], pos[1] + DIRS[d][1]) + return path, pos, d + + +def _inspect(path, p, d): + pk = path.keys() + startx = min(map(lambda yx: yx[1], pk)) - 1 + endx = max(map(lambda yx: yx[1], pk)) + 2 + starty = min(map(lambda yx: yx[0], pk)) - 1 + endy = max(map(lambda yx: yx[0], pk)) + 2 + + matrix = [ + [COLORS[path[(y, x)]] for x in range(startx, endx)] for y in range(starty, endy) + ] + y, x = p + matrix[abs(starty) + y][abs(startx) + x] = "^"[d] + + return "\n".join(["".join(line) for line in matrix]) + + +if __name__ == "__main__": + parsed = parse_input() + part_1(parsed) + part_2(parsed) # JZPJRAGJ diff --git a/2019-python/output/intcode_computer.py b/2019-python/output/intcode_computer.py index 95630cf..e587ef2 100644 --- a/2019-python/output/intcode_computer.py +++ b/2019-python/output/intcode_computer.py @@ -7,6 +7,15 @@ intcode computer, AoC 2019 Changelog ========= +0.3.2 +----- + +Patch release (day 9 part 1-2, day 11 part 1-2). + +- Return relative base upon input suspension +- Improve intcode debugger +- Fix errorous state restoration upon resuming upon input + 0.3.1 ----- @@ -71,7 +80,7 @@ Initial version (day 2 part 1). - Add operation 1: adds parameter 1 to parameter 2, store to parameter 3 position - Add operation 2: multiply parameter 1 with parameter 2, store to parameter 3 position """ -__version__ = "0.3.1" +__version__ = "0.3.2" def parse(data): @@ -87,61 +96,42 @@ def execute( interactive=False, verbose=False, n=0, + rb=0, ): if verbose: - title = f"intcode computer, version {v}" + title = f"intcode computer, version {__version__}" print("".join("=" for _ in title)) print(title) print("".join("=" for _ in title)) state = defaultdict(int) - for k, v in zip(range(len(program)), program): - state[k] = v + if isinstance(program, list): + for k, v in zip(range(len(program)), program): + state[k] = v + else: + state = program.copy() if noun: state[1] = noun if verb: state[2] = verb - c = 0 - rb = 0 stdout = [] - if not isinstance(stdin, list): - stdin = [stdin] def halt(code): - return code, state, n, stdout - - def values(modes, *parameters): - for i, v in enumerate(parameters): - if modes[i] == "0" and v < 0: - print("================ ERROR =================") - print("Negative index provided to position mode") - if modes[i] == "2" and rb + v < 0: - print("================ ERROR =================") - print("Negative index provided to relative mode") - - def value(i, v): - if modes[i] == "1": - return v - if modes[i] == "2": - return state[v + rb] - return state[v] - - if len(parameters) > 1: - return [value(i, v) for i, v in enumerate(parameters)] - return value(0, parameters[0]) + return code, state, n, rb, stdout + if debug and n > 0: + print(f"@{str(n).zfill(4)} [resuming program, stdin={stdin}]") while True: instruction = state[n] - # if instruction > 200 and instruction < 1000: - # print("") - # spn = 2 if instruction % 100 % 3 == 0 else 4 - # print(list(state.values())[n : n + spn]) opcode = instruction % 100 modes = str(instruction // 100).zfill(3)[::-1] + if opcode not in (1, 2, 3, 4, 5, 6, 7, 8, 9, 99): + print("opcode={opcode} not implemented, halting") + return halt(-2) if opcode == 1: a = state[n + 1] b = state[n + 2] c = n + 3 - x, y = values(modes, a, b) + x, y = _values(state, modes, rb, a, b) p = state[c] if modes[2] == "2": p += rb @@ -153,7 +143,7 @@ def execute( a = state[n + 1] b = state[n + 2] c = n + 3 - x, y = values(modes, a, b) + x, y = _values(state, modes, rb, a, b) p = state[c] if modes[2] == "2": p += rb @@ -174,15 +164,17 @@ def execute( state[p] = stdin.pop(0) else: if interactive: - state[p] = int(input("> ")) + manual = int(input("> ")) + state[p] = manual + print(f"set STDIN to {manual} at pos {p}") else: if debug: - print(f"@{str(n).zfill(4)} [suspended, awaiting input]") + print(f"@{str(n).zfill(4)} [suspended, awaiting input ...]") return halt(3) n += 2 if opcode == 4: a = state[n + 1] - x = values(modes, a) + x = _values(state, modes, rb, a) stdout.append(x) if verbose: print(x) @@ -192,7 +184,7 @@ def execute( if opcode == 5: a = state[n + 1] b = state[n + 2] - x, y = values(modes, a, b) + x, y = _values(state, modes, rb, a, b) if x != 0: if debug: print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-1 | {x} != 0, n={y}") @@ -204,18 +196,20 @@ def execute( if opcode == 6: a = state[n + 1] b = state[n + 2] - x, y = values(modes, a, b) + x, y = _values(state, modes, rb, a, b) if x == 0: + if debug: + print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-0 | {x} == 0, n={y}") n = y else: + if debug: + print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-0 | {x} == 0, ignoring") n += 3 - if debug: - print(f"{n}:{opcode} | {n}") if opcode == 7: a = state[n + 1] b = state[n + 2] c = n + 3 - x, y = values(modes, a, b) + x, y = _values(state, modes, rb, a, b) p = state[c] if modes[2] == "2": p += rb @@ -227,7 +221,7 @@ def execute( a = state[n + 1] b = state[n + 2] c = n + 3 - x, y = values(modes, a, b) + x, y = _values(state, modes, rb, a, b) p = state[c] if modes[2] == "2": p += rb @@ -237,18 +231,35 @@ def execute( n += 4 if opcode == 9: a = state[n + 1] - x = values(modes, a) + x = _values(state, modes, rb, a) if debug: print(f"@{str(n).zfill(4)} {opcode}_RELBASE | {rb} + {x}") rb += x n += 2 if opcode == 99: - break - c += 1 - if debug and c % 1000 == 0: - print(f"{c} instructions done, current pos: {n}") - if c == 33: - break - if verbose: - title = f"intcode computer received SIGTERM" - return halt(99) + if debug: + print(f"@{str(n).zfill(4)} {opcode}_HALT | n={n}") + return halt(99) + return halt(-1) + + +def _values(state, modes, rb, *parameters): + # for i, v in enumerate(parameters): + # if modes[i] == "0" and v < 0: + # print("================ ERROR =================") + # print("Negative index provided to position mode") + # if modes[i] == "2" and rb + v < 0: + # print("================ ERROR =================") + # print("Negative index provided to relative mode") + + if len(parameters) > 1: + return [_value(state, modes, rb, k, v) for k, v in enumerate(parameters)] + return _value(state, modes, rb, 0, parameters[0]) + + +def _value(state, modes, rb, m, s): + if modes[m] == "1": + return s + if modes[m] == "2": + return state[s + rb] + return state[s] -- 2.45.3 From 5cb2dccf62c89d5ba51ba18ba38c24dd75a47087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Thu, 14 Dec 2023 22:46:15 +0100 Subject: [PATCH 13/19] Deprecate nouns and verbs arguments for intcode --- 2019-python/output/day_02.py | 9 ++++++--- 2019-python/output/intcode_computer.py | 15 ++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/2019-python/output/day_02.py b/2019-python/output/day_02.py index e9e4465..29ab8cb 100644 --- a/2019-python/output/day_02.py +++ b/2019-python/output/day_02.py @@ -1,5 +1,4 @@ from output import answer, puzzleinput -from collections import defaultdict from output.intcode_computer import execute, parse @@ -14,13 +13,17 @@ def parse_input(data): @answer(1, "[intcode-0.1.0] Value of pos 0 is {} at halt signal") def part_1(program): - _code, state, *_unused = execute(program, noun=12, verb=2) + program[1] = 12 + program[2] = 2 + _code, state, *_unused = execute(program) return state[0] @answer(2, "[intcode-0.1.1] 100 * noun + verb = {} for output 19690720") def part_2(program, noun=76, verb=21): - _code, state, *_unused = execute(program, noun, verb) + program[1] = noun + program[2] = verb + _code, state, *_unused = execute(program) if state[0] == 19690720: return 100 * noun + verb return state[0] diff --git a/2019-python/output/intcode_computer.py b/2019-python/output/intcode_computer.py index e587ef2..240bf7c 100644 --- a/2019-python/output/intcode_computer.py +++ b/2019-python/output/intcode_computer.py @@ -7,6 +7,13 @@ intcode computer, AoC 2019 Changelog ========= +0.3.3 +----- + +Patch release (no specific day) + +- Deprecating noun and verb. Those are now up to consumer to provide. + 0.3.2 ----- @@ -80,7 +87,7 @@ Initial version (day 2 part 1). - Add operation 1: adds parameter 1 to parameter 2, store to parameter 3 position - Add operation 2: multiply parameter 1 with parameter 2, store to parameter 3 position """ -__version__ = "0.3.2" +__version__ = "0.3.3" def parse(data): @@ -89,8 +96,6 @@ def parse(data): def execute( program, - noun=None, - verb=None, stdin=[], debug=False, interactive=False, @@ -109,10 +114,6 @@ def execute( state[k] = v else: state = program.copy() - if noun: - state[1] = noun - if verb: - state[2] = verb stdout = [] def halt(code): -- 2.45.3 From 051c3f3ebdd0c6394364c55d4ba6c2a4e6c59fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Tue, 19 Dec 2023 18:34:03 +0100 Subject: [PATCH 14/19] Lint and Refactor day 1-11 --- 2019-python/aoc.py | 101 +++++++++++++++---------- 2019-python/output/__init__.py | 77 ++++++++++++++----- 2019-python/output/day_01.py | 38 ++++++---- 2019-python/output/day_02.py | 49 +++++++----- 2019-python/output/day_03.py | 57 +++++++------- 2019-python/output/day_04.py | 48 +++++++----- 2019-python/output/day_05.py | 41 ++++++---- 2019-python/output/day_06.py | 46 ++++++----- 2019-python/output/day_07.py | 48 +++++++----- 2019-python/output/day_08.py | 44 +++++++---- 2019-python/output/day_09.py | 39 ++++++---- 2019-python/output/day_10.py | 41 ++++++---- 2019-python/output/day_11.py | 43 +++++++---- 2019-python/output/intcode_computer.py | 4 +- 14 files changed, 422 insertions(+), 254 deletions(-) diff --git a/2019-python/aoc.py b/2019-python/aoc.py index 07a42b2..8dcdaa9 100644 --- a/2019-python/aoc.py +++ b/2019-python/aoc.py @@ -1,5 +1,12 @@ +import os import sys + +def headline(n, title): + """Print day number and name, followed by a ruler. Used by the answer decorator""" + print(f"\n--- Day {n}: {title} ---\n") + + year = 2019 try: @@ -19,48 +26,54 @@ if day_no and name: with open("output/day_{}.py".format(padded_no), "w") as s: s.write( f""" -from output import answer, puzzleinput +from output import answer # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg n = {day_no} title = "{name}" -@puzzleinput(n) -def parse_input(data): - return data - - @answer(1, "Answer is {{}}") -def part_1(data): - return data +def part_1(outputs): + return outputs[0] @answer(2, "Actually, answer is {{}}") -def part_2(data): - return data +def part_2(outputs): + return outputs[1] + + +def solve(data): + return None, None if __name__ == "__main__": # use dummy data - parsed = \"\"\" + inp = \"\"\" replace me \"\"\".strip() # uncomment to instead use stdin - # import fileinput - # parsed = "\\n".join(list(fileinput.input())) + # import sys; inp = sys.stdin.read().strip() - # uncomment to instead use content of input/{padded_no}.txt - # parsed = parse_input() + # uncomment to use AoC provided puzzle input + # with open("./input/{padded_no}.txt", "r") as f: + # inp = f.read().strip() - part_1(parsed) - # part_2(parsed) + inp = solve(inp) + + a = part_1(inp) + # b = part_2(inp) + + # uncomment and replace 0 with actual output to refactor code + # and ensure nonbreaking changes + # assert a == 0 + # assert b == 0 """.strip() + "\n" ) - print(f"- creating empty input/{day_no.zfill(2)}.txt") - with open("input/{}.txt".format(day_no.zfill(2)), "w") as i: - i.write("") + print("- making sure input dir exists") + if not os.path.exists("input"): + os.makedirs("input") print( f""" @@ -75,28 +88,34 @@ https://adventofcode.com/{year}/day/{day_no}/input ) exit(0) -from output import headline stars = 0 for i in [str(n).zfill(2) for n in range(1, 26)]: - try: - day = __import__( - "output.day_{}".format(i), - globals(), - locals(), - ["n", "title", "part_1", "part_2", "parse_input"], - 0, - ) - headline(day.n, day.title) - data = day.parse_input() - day.part_1(data, decorate=True) - stars += 1 - day.part_2(data, decorate=True) - stars += 1 - except IOError: - pass - except ImportError: - pass -print(f"\nStars: {stars}") -print("".join("*" if n <= stars else "•" for n in range(50))) + if not day_no or day_no.zfill(2) == i: + try: + day = __import__( + "output.day_{}".format(i), + globals(), + locals(), + ["n", "title", "part_1", "part_2", "solve"], + 0, + ) + with open(f"./input/{i}.txt", "r") as f: + data = f.read().strip() + headline(day.n, day.title) + try: + data = day.solve(data) + except AttributeError: + pass + if day.part_1(data, decorate=True): + stars += 1 + if day.part_2(data, decorate=True): + stars += 1 + except IOError: + pass + except ImportError: + pass +if not day_no: + print(f"\nStars: {stars}") + print("".join("*" if n < stars else "•" for n in range(50))) print("") diff --git a/2019-python/output/__init__.py b/2019-python/output/__init__.py index 4483fa4..3a034d6 100644 --- a/2019-python/output/__init__.py +++ b/2019-python/output/__init__.py @@ -1,26 +1,38 @@ import functools +import re +# Directions/Adjacents for 2D matrices, in the order UP, RIGHT, DOWN, LEFT +D = [ + (-1, 0), + (0, 1), + (1, 0), + (0, -1), +] -def puzzleinput(n, **kwargs): - filename = str(n).zfill(2) - trim_input = kwargs.get("trim_input", True) - filepath = f"./input/{filename}.txt" +# Directions for 2D matrices, as a dict with keys U, R, D, L +DD = { + "U": (-1, 0), + "R": (0, 1), + "D": (1, 0), + "L": (0, -1), +} - def decorator_pi(func): - @functools.wraps(func) - def wrapper_pi(*args, **kwargs): - with open(filepath, "r") as f: - data = f.read() - if trim_input: - return func(data.strip(), *args, **kwargs) - return func(data, *args, **kwargs) - - return wrapper_pi - - return decorator_pi +# Adjacent relative positions including diagonals for 2D matrices, in the order NW, N, NE, W, E, SW, S, SE +ADJ = [ + (-1, -1), + (-1, 0), + (1, -1), + (0, -1), + (0, 1), + (1, 1), + (1, 0), + (1, -1), +] def answer(part_index, fmt_string): + """Decorator to present a solution in a human readable format""" + def decorator_aoc(func): @functools.wraps(func) def wrapper_aoc(*args, **kwargs): @@ -32,7 +44,7 @@ def answer(part_index, fmt_string): print(answer) else: formatted = fmt_string.format(answer) - print(f"[part {part_index}] {formatted}") + print(f" {part_index}) {formatted}") return answer return wrapper_aoc @@ -40,6 +52,31 @@ def answer(part_index, fmt_string): return decorator_aoc -def headline(n, title): - title = f"Day {n}: {title}" - print("\n".join(["", title, "".join("-" for _ in title), ""])) +def ints(s): + """Extract all integers from a string""" + return [int(n) for n in re.findall(r"\d+", s)] + + +def mhd(a, b): + """Calculates the Manhattan distance between 2 positions in the format (y, x) or (x, y)""" + ar, ac = a + br, bc = b + return abs(ar - br) + abs(ac - bc) + + +def matrix(d): + """Transform a string into an iterable matrix. Returns the matrix, row count and col count""" + m = [tuple(r) for r in d.split()] + return m, len(m), len(m[0]) + + +def mdbg(m): + """Print-debug a matrix""" + for r in m: + print("".join(r)) + + +def vdbg(seen, h, w): + """Print-debug visited positions of a matrix""" + for r in range(h): + print("".join(["#" if (r, c) in seen else "." for c in range(w)])) diff --git a/2019-python/output/day_01.py b/2019-python/output/day_01.py index 64a49f7..86179b6 100644 --- a/2019-python/output/day_01.py +++ b/2019-python/output/day_01.py @@ -1,32 +1,40 @@ -from output import answer, puzzleinput +from output import answer n = 1 title = "The Tyranny of the Rocket Equation" -@puzzleinput(n) -def parse_input(data): - return list(map(int, data.split())) - - @answer(1, "Total fuel requirements are {}") -def part_1(lines): - return sum(n // 3 - 2 for n in lines) +def part_1(o): + return o[0] @answer(2, "Total fuel requirements are {} including fuel costs") -def part_2(lines): - s = 0 +def part_2(o): + return o[1] + + +def solve(data): + lines = list(map(int, data.split())) + p1 = sum(n // 3 - 2 for n in lines) + p2 = 0 for fuel in lines: rem = fuel while rem > 0: cost = rem // 3 - 2 - s += max(0, cost) + p2 += max(0, cost) rem = max(0, cost) - return s + return p1, p2 if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) + with open("./input/01.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 3393938 + assert b == 5088037 diff --git a/2019-python/output/day_02.py b/2019-python/output/day_02.py index 29ab8cb..0bd3732 100644 --- a/2019-python/output/day_02.py +++ b/2019-python/output/day_02.py @@ -1,35 +1,48 @@ -from output import answer, puzzleinput - +from output import answer from output.intcode_computer import execute, parse n = 2 title = "1202 Program Alarm" -@puzzleinput(n) -def parse_input(data): - return parse(data) - - @answer(1, "[intcode-0.1.0] Value of pos 0 is {} at halt signal") -def part_1(program): - program[1] = 12 - program[2] = 2 - _code, state, *_unused = execute(program) - return state[0] +def part_1(o): + return o[0] @answer(2, "[intcode-0.1.1] 100 * noun + verb = {} for output 19690720") -def part_2(program, noun=76, verb=21): +def part_2(o): + return o[1] + + +def solve(data): + program = parse(data) + + program[1] = 12 + program[2] = 2 + _code, state, *_unused = execute(program) + noun = 76 # found manually by binary search + verb = 21 + p1 = state[0] + program[1] = noun program[2] = verb _code, state, *_unused = execute(program) + p2 = state[0] if state[0] == 19690720: - return 100 * noun + verb - return state[0] + p2 = 100 * noun + verb + + return p1, p2 if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed, 76, 21) # found manually by binary search + with open("./input/02.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 3306701 + assert b == 7621 diff --git a/2019-python/output/day_03.py b/2019-python/output/day_03.py index 79967b1..0b3277f 100644 --- a/2019-python/output/day_03.py +++ b/2019-python/output/day_03.py @@ -1,5 +1,6 @@ from collections import defaultdict -from output import answer, puzzleinput + +from output import answer n = 3 title = "Crossed Wires" @@ -12,37 +13,24 @@ directions = { } -@puzzleinput(n) -def parse_input(data): - return [line.split(",") for line in data.split()] - - @answer( 1, "As the crow flies, closest intersection Manhattan distance is {} units away" ) -def part_1(wires): - def follow(instructions): - seen = [] - pos = (0, 0) - for instruction in instructions: - urdl, *l = instruction - distance = int("".join(l)) - for _ in range(distance): - pos = (pos[0] + directions[urdl][0], pos[1] + directions[urdl][1]) - seen.append(pos) - return set(seen) - - wa = follow(wires[0]) - wb = follow(wires[1]) - - return min(sum(map(abs, i)) for i in wa & wb) +def part_1(o): + return o[0] @answer(2, "By travel, closest intersection Manhattan distance is {} units away") -def part_2(wires): +def part_2(o): + return o[1] + + +def solve(inp): + wires = [line.split(",") for line in inp.split()] seen = defaultdict(dict) def follow(instructions, i): + visited = [] steps = 0 pos = (0, 0) for instruction in instructions: @@ -51,16 +39,29 @@ def part_2(wires): for _ in range(distance): steps += 1 pos = (pos[0] + directions[urdl][0], pos[1] + directions[urdl][1]) + visited.append(pos) if i not in seen[pos]: seen[pos][i] = steps + return set(visited) + p1w = [] for i, wire in enumerate(wires): - follow(wire, i) + p1w.append(follow(wire, i)) + p1 = min(sum(map(abs, i)) for i in p1w[0] & p1w[1]) - return min(sum(v.values()) for v in seen.values() if len(v) > 1) + p2 = min(sum(v.values()) for v in seen.values() if len(v) > 1) + + return p1, p2 if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) + with open("./input/03.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 1337 + assert b == 65356 diff --git a/2019-python/output/day_04.py b/2019-python/output/day_04.py index d0464b9..45a5e69 100644 --- a/2019-python/output/day_04.py +++ b/2019-python/output/day_04.py @@ -1,36 +1,44 @@ from collections import Counter -from output import answer, puzzleinput + +from output import answer n = 4 title = "Secure Container" -@puzzleinput(n) -def parse_input(data): - return data.split("-") - - @answer(1, "{} combinations of valid passwords") -def part_1(range_values): - a, b = range_values - - def valid(s): - return "".join(sorted(s)) == s and any(x == y for x, y in zip(s, s[1:])) - - return sum(valid(str(pw)) for pw in range(int(a), int(b) + 1)) +def part_1(o): + return o[0] @answer(2, "{} combinations of valid passwords, including important detail") -def part_2(range_values): - a, b = range_values +def part_2(o): + return o[1] - def valid(s): + +def solve(data): + a, b = data.split("-") + + def v1(s): + return "".join(sorted(s)) == s and any(x == y for x, y in zip(s, s[1:])) + + def v2(s): return "".join(sorted(s)) == s and 2 in Counter(s).values() - return sum(valid(str(pw)) for pw in range(int(a), int(b) + 1)) + p1 = sum(v1(str(pw)) for pw in range(int(a), int(b) + 1)) + p2 = sum(v2(str(pw)) for pw in range(int(a), int(b) + 1)) + + return p1, p2 if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) + with open("./input/04.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 544 + assert b == 334 diff --git a/2019-python/output/day_05.py b/2019-python/output/day_05.py index 179e8c3..3075f49 100644 --- a/2019-python/output/day_05.py +++ b/2019-python/output/day_05.py @@ -1,29 +1,40 @@ -from output import answer, puzzleinput - +from output import answer from output.intcode_computer import execute, parse n = 5 title = "Sunny with a Chance of Asteroids" -@puzzleinput(n) -def parse_input(data): - return parse(data) - - @answer(1, "[intcode-0.2.0] Program diagnostic code, ID 1: {}") -def part_1(program): - _code, _state, _cursorpos, rb, stdout = execute(program, stdin=[1]) - return max(stdout) +def part_1(o): + return o[0] @answer(2, "[intcode-0.2.1] Program diagnostic code, ID 5: {}") -def part_2(program): +def part_2(o): + return o[1] + + +def solve(data): + program = parse(data) + + _code, _state, _cursorpos, rb, stdout = execute(program, stdin=[1]) + p1 = max(stdout) + _code, _state, _cursorpos, rb, stdout = execute(program, stdin=[5]) - return stdout[0] + p2 = stdout[0] + + return p1, p2 if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) + with open("./input/05.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 16434972 + assert b == 16694270 diff --git a/2019-python/output/day_06.py b/2019-python/output/day_06.py index 3d4f201..9ad8f70 100644 --- a/2019-python/output/day_06.py +++ b/2019-python/output/day_06.py @@ -1,35 +1,40 @@ -from collections import defaultdict, deque -from output import answer, puzzleinput +from collections import defaultdict + +from output import answer n = 6 title = "Universal Orbit Map" -@puzzleinput(n) -def parse_input(data): - heritage = defaultdict(str) - for parent, child in [line.split(")") for line in data.split()]: - heritage[child] = parent - return heritage - - @answer(1, "{} direct and indirect orbits") -def part_1(heritage): - return sum(len(ancestry(heritage, v)) for v in heritage.keys()) +def part_1(o): + return o[0] @answer(2, "Orbit transfers needed for you to share orbit with Santa: {}") -def part_2(heritage): +def part_2(o): + return o[1] + + +def solve(data): + heritage = defaultdict(str) + for parent, child in [line.split(")") for line in data.split()]: + heritage[child] = parent + + p1 = sum(len(ancestry(heritage, v)) for v in heritage.keys()) + a = ancestry(heritage, "YOU") b = ancestry(heritage, "SAN") shared = len(set(a) & set(b)) - return sum( + p2 = sum( [ len(a) - shared, len(b) - shared, ] ) + return p1, p2 + def ancestry(parents, child): k = child @@ -41,6 +46,13 @@ def ancestry(parents, child): if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) + with open("./input/06.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 271151 + assert b == 388 diff --git a/2019-python/output/day_07.py b/2019-python/output/day_07.py index 2e90e5e..6b19166 100644 --- a/2019-python/output/day_07.py +++ b/2019-python/output/day_07.py @@ -1,23 +1,32 @@ from collections import defaultdict from itertools import permutations -from output import answer, puzzleinput +from output import answer from output.intcode_computer import execute, parse n = 7 title = "Amplification Circuit" -@puzzleinput(n) -def parse_input(data): - return parse(data) - - @answer( 1, "[intcode 0.3.0] The highest achievable signal to the thruster is {}", ) -def part_1(program): +def part_1(o): + return o[0] + + +@answer( + 2, + "[intcode 0.3.0] By creating a feedback loop, the highest achievable signal to the thruster is {}", +) +def part_2(o): + return o[0] + + +def solve(data): + program = parse(data) + thruster_signals = [] for settings in map(list, permutations(range(5))): o = 0 @@ -25,14 +34,8 @@ def part_1(program): _code, _state, _n, _rb, so = execute(program, stdin=[ps, o]) o = so.pop(0) thruster_signals.append(o) - return max(thruster_signals) + p1 = max(thruster_signals) - -@answer( - 2, - "[intcode 0.3.0] By creating a feedback loop, the highest achievable signal to the thruster is {}", -) -def part_2(program): thruster_signals = [] for settings in map(list, permutations(range(5, 10))): o = [0] @@ -56,10 +59,19 @@ def part_2(program): finished.add(amp) o = so thruster_signals.append(o[-1]) - return max(thruster_signals) + p2 = max(thruster_signals) + + return p1, p2 if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) + with open("./input/07.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 398674 + assert b == 39431233 diff --git a/2019-python/output/day_08.py b/2019-python/output/day_08.py index 3abd526..30ad1e0 100644 --- a/2019-python/output/day_08.py +++ b/2019-python/output/day_08.py @@ -1,38 +1,50 @@ from collections import Counter from textwrap import wrap -from output import answer, puzzleinput + +from output import answer n = 8 title = "Space Image Format" -@puzzleinput(n) -def parse_input(data): - return data - - @answer(1, "The product of all 1s and 2s in the layer with fewest 0s is {}") -def part_1(data): +def part_1(o): + return o[0] + + +@answer(2, "The message is {}, the decoded image looks like above") +def part_2(o): + return o[1] + + +def solve(data): layers = sorted(map(Counter, wrap(data, 25 * 6)), key=lambda c: c["0"]) + width, height = 25, 6 a = layers[0]["1"] b = layers[0]["2"] - return a * b + p1 = a * b - -@answer(2, "The message is CYUAH, the decoded image looks like this:\n\n{}") -def part_2(data, width=25, height=6): layers = wrap(data, width * height) - l = len(layers) pixels = zip(*layers) lit = map( lambda s: s.replace("0", ".").replace("1", "#"), map(lambda p: next(filter(lambda x: x != "2", p)), pixels), ) matrix = "\n".join(wrap("".join(lit), width)) - return matrix + print(matrix) + p2 = "CYUAH" + + return p1, p2 if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) + with open("./input/08.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 2500 + assert b == "CYUAH" diff --git a/2019-python/output/day_09.py b/2019-python/output/day_09.py index 5d118dc..1e25fa9 100644 --- a/2019-python/output/day_09.py +++ b/2019-python/output/day_09.py @@ -1,25 +1,27 @@ -from output import answer, puzzleinput +from output import answer from output.intcode_computer import execute, parse n = 9 title = "Sensor Boost" -@puzzleinput(n) -def parse_input(data): - return parse(data) - - @answer(1, "[intcode 0.3.1] BOOST keycode: {}") -def part_1(program): - _c, _s, _n, _rb, outputs = execute(program, stdin=[1]) - return outputs.pop(0) +def part_1(o): + return o[0] @answer(2, "[intcode 0.3.1] Distress signal coordinates: {}") -def part_2(program): - _c, _s, _n, _rb, outputs = execute(program, stdin=[2]) - return outputs.pop(0) +def part_2(o): + return o[1] + + +def solve(data): + program = parse(data) + p12 = [] + for inp in [1, 2]: + _c, _s, _n, _rb, outputs = execute(program, stdin=[inp]) + p12.append(outputs.pop(0)) + return p12 if __name__ == "__main__": @@ -72,8 +74,13 @@ if __name__ == "__main__": assert execute([109, 1, 203, 2, 204, 2, 99], stdin=[666])[4][0] == 666 assert execute([109, 6, 21001, 9, 25, 1, 104, 0, 99, 49])[4][0] == 74 - parsed = parse_input() - assert execute(parsed, stdin=[1])[4][0] == 2351176124 + with open("./input/09.txt", "r") as f: + inp = f.read().strip() - part_1(parsed) - part_2(parsed) + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 2351176124 + assert b == 73110 diff --git a/2019-python/output/day_10.py b/2019-python/output/day_10.py index ffaaa37..4e8fcc2 100644 --- a/2019-python/output/day_10.py +++ b/2019-python/output/day_10.py @@ -1,28 +1,31 @@ from collections import OrderedDict, defaultdict, deque from math import atan2 -from output import answer, puzzleinput + +from output import answer n = 10 title = "Monitoring Station" -@puzzleinput(n) -def parse_input(data): - return data.strip().split() - - @answer(1, "The monitor station will see {} asteroids at best") -def part_1(matrix): - _pos, visible = _map_visible_asteroids(matrix) - return len(set(dict(visible).values())) +def part_1(o): + return o[0] @answer( 2, "The asteroid at y=3 x=17 (checksum {}) will be the 200th lazer vapored asteroid, making some elf happy", ) -def part_2(matrix): +def part_2(o): + return o[1] + + +def solve(data): + matrix = data.strip().split() pos, visible = _map_visible_asteroids(matrix) + + p1 = len(set(dict(visible).values())) + targets_upper = defaultdict(list) targets_lower = defaultdict(list) targets = dict() @@ -58,7 +61,10 @@ def part_2(matrix): break if not popped: break - return x * 100 + y + + p2 = x * 100 + y + + return p1, p2 def _map_visible_asteroids(matrix): @@ -78,6 +84,13 @@ def _map_visible_asteroids(matrix): if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) + with open("./input/10.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 292 + assert b == 317 diff --git a/2019-python/output/day_11.py b/2019-python/output/day_11.py index 6f4d790..683656a 100644 --- a/2019-python/output/day_11.py +++ b/2019-python/output/day_11.py @@ -1,16 +1,12 @@ from collections import defaultdict + +from output import answer from output.intcode_computer import execute, parse -from output import answer, puzzleinput n = 11 title = "Space Police" -@puzzleinput(n) -def parse_input(data): - return parse(data) - - DIRS = [ (-1, 0), (0, -1), @@ -26,18 +22,28 @@ TL = ["RIGHT", "LEFT"] @answer(1, "[intcode 0.3.2] Robot paints {} panes at least once") -def part_1(program): - path, pos, d = _paint(program) - return len(path) +def part_1(o): + return o[0] @answer( 2, - '[intcode 0.3.2] The hull has registration identifier "JZPJRAGJ" freshly painted, see below: \n\n{}', + '[intcode 0.3.2] The hull has registration identifier "{}" freshly painted, see above', ) -def part_2(program): +def part_2(o): + return o[1] + + +def solve(data): + program = parse(data) + path, pos, d = _paint(program) + p1 = len(path) + path, pos, d = _paint(program, 1) - return _inspect(path.copy(), pos, d) + print(_inspect(path.copy(), pos, d)) + p2 = "JZPJRAGJ" + + return p1, p2 def _paint(program, initial=0): @@ -77,6 +83,13 @@ def _inspect(path, p, d): if __name__ == "__main__": - parsed = parse_input() - part_1(parsed) - part_2(parsed) # JZPJRAGJ + with open("./input/11.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 2720 + assert b == "JZPJRAGJ" diff --git a/2019-python/output/intcode_computer.py b/2019-python/output/intcode_computer.py index 240bf7c..418ee32 100644 --- a/2019-python/output/intcode_computer.py +++ b/2019-python/output/intcode_computer.py @@ -1,5 +1,7 @@ +import sys from collections import defaultdict +sys.set_int_max_str_digits(999_999) """ intcode computer, AoC 2019 @@ -91,7 +93,7 @@ __version__ = "0.3.3" def parse(data): - return list(map(int, data.split(","))) + return [int(s) for s in data.split(",")] def execute( -- 2.45.3 From d01b4aa0f857154ec3c4e702a2f894956cad9644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Wed, 27 Dec 2023 13:39:10 +0100 Subject: [PATCH 15/19] Solve 2019:12 "The N-Body Problem" Also, fix bug in ints() helper. --- 2019-python/output/__init__.py | 2 +- 2019-python/output/day_12.py | 109 +++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 2019-python/output/day_12.py diff --git a/2019-python/output/__init__.py b/2019-python/output/__init__.py index 3a034d6..7fa2647 100644 --- a/2019-python/output/__init__.py +++ b/2019-python/output/__init__.py @@ -54,7 +54,7 @@ def answer(part_index, fmt_string): def ints(s): """Extract all integers from a string""" - return [int(n) for n in re.findall(r"\d+", s)] + return [int(n) for n in re.findall(r"-?\d+", s)] def mhd(a, b): diff --git a/2019-python/output/day_12.py b/2019-python/output/day_12.py new file mode 100644 index 0000000..a743b53 --- /dev/null +++ b/2019-python/output/day_12.py @@ -0,0 +1,109 @@ +from itertools import combinations +from math import lcm + +from output import answer, ints # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg + +n = 12 +title = "The N-Body Problem" + + +@answer(1, "Answer is {}") +def part_1(outputs): + return outputs[0] + + +@answer(2, "Actually, answer is {}") +def part_2(outputs): + return outputs[1] + + +def solve(data): + M = [[ints(l), [0, 0, 0]] for l in data.splitlines()] + + def G(m): + def U(x, y): + if x > y: + return -1 + elif x < y: + return 1 + return 0 + + for i, j in combinations(range(len(m)), r=2): + m1 = m[i] + m2 = m[j] + g1, v1 = m1 + g2, v2 = m2 + x1, y1, z1 = g1 + x2, y2, z2 = g2 + a1, b1, c1 = v1 + a2, b2, c2 = v2 + m[i] = [ + g1, + [ + a1 + U(x1, x2), + b1 + U(y1, y2), + c1 + U(z1, z2), + ], + ] + m[j] = [ + g2, + [ + a2 + U(x2, x1), + b2 + U(y2, y1), + c2 + U(z2, z1), + ], + ] + + return m + + def V(m): + nm = [] + for gv in m: + g, v = gv + x1, y1, z1 = g + x2, y2, z2 = v + nm.append( + [ + [ + x1 + x2, + y1 + y2, + z1 + z2, + ], + v, + ] + ) + return nm + + P1 = M.copy() + for _ in range(1000): + P1 = V(G(P1)) + p1 = sum(sum(map(abs, p)) * sum(map(abs, k)) for p, k in P1) + P2 = M.copy() + p2 = [] + for i, igv in enumerate(zip(*[g for g, v in P2])): + igv = [(g, 0) for g in igv] + Q = P2.copy() + C = 0 + while True: + Q = V(G(Q)) + C += 1 + sg = list(zip(*[g for g, v in Q]))[i] + sv = list(zip(*[v for g, v in Q]))[i] + if list(zip(sg, sv)) == igv: + p2.append(C) + break + p2 = lcm(*p2) + return p1, p2 + + +if __name__ == "__main__": + with open("./input/12.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 12466 + assert b == 360689156787864 -- 2.45.3 From 40274ea044100520417c30a4320e41fa7ba1fb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Fri, 29 Dec 2023 00:36:55 +0100 Subject: [PATCH 16/19] Solve 2019:14 "Space Stoichiometry" Got stuck completely at part 1. Last 2 examples did not add up, off by 100 ore. I did know that I had to count by overflows somehow, but I could not manage it. After countless hours writing a verbose debugger, I wiped the whole thing and looked at this code for inspiration: https://github.com/sophiebits/adventofcode/blob/master/2019/day14.py It works by using modulus and removing all keys in a dict except ORE. --- 2019-python/output/day_14.py | 69 ++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 2019-python/output/day_14.py diff --git a/2019-python/output/day_14.py b/2019-python/output/day_14.py new file mode 100644 index 0000000..45962a4 --- /dev/null +++ b/2019-python/output/day_14.py @@ -0,0 +1,69 @@ +from collections import defaultdict + +from output import answer # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg + +n = 14 +title = "Space Stoichiometry" + +BAEST = 1_000_000_000_000 + + +@answer(1, "Answer is {}") +def part_1(outputs): + return outputs[0] + + +@answer(2, "Actually, answer is {}") +def part_2(outputs): + return outputs[1] + + +def solve(data, verbose=False): + T = defaultdict(lambda: [0, {}]) + for l in data.splitlines(): + i, o = l.split(" => ") + a, o = o.split() + T[o][0] += int(a) + for vk in i.split(", "): + v, k = vk.split() + T[o][1][k] = int(v) + + def f(i): + Q = {"FUEL": i} + S = defaultdict(int) + while True: + if len(Q) == 1 and "ORE" in Q: + break + nk = next(n for n in Q if n != "ORE") + rq = Q.pop(nk) + q, r = T[nk] + d = rq // q + m = rq % q + if m > 0: + S[nk] = q - m + d += 1 + + for k, v in r.items(): + Q[k] = Q.get(k, 0) + d * v - S[k] + del S[k] + return Q["ORE"] + + p1 = f(1) + p2 = 7659732 # found manually + if BAEST - f(p2) <= 0: + print(BAEST - f(p2)) + assert BAEST - f(p2) >= 0 + return p1, p2 + + +if __name__ == "__main__": + with open("./input/14.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == 198984 + assert b == 7659732 -- 2.45.3 From 107ca5ab7c112d6dd01dad5e7e544bb7efe336d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Fri, 29 Dec 2023 11:54:21 +0100 Subject: [PATCH 17/19] Solve 2019:16 "Flawed Frequency Transmission" Solution takes several hours to run, commented out and cached. --- 2019-python/output/day_16.py | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 2019-python/output/day_16.py diff --git a/2019-python/output/day_16.py b/2019-python/output/day_16.py new file mode 100644 index 0000000..722924c --- /dev/null +++ b/2019-python/output/day_16.py @@ -0,0 +1,53 @@ +from output import answer # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg + +n = 16 +title = "Flawed Frequency Transmission" + + +BAEST = 10_000 + + +@answer(1, "Answer is {}") +def part_1(outputs): + return outputs[0] + + +@answer(2, "Actually, answer is {}") +def part_2(outputs): + return outputs[1] + + +def solve(data): + bp = [0, 1, 0, -1] + o = int(data[:7]) + s = [int(c) for c in data] + s2 = s * BAEST + + for _ in range(100): + s = [ + abs(sum(d * bp[j // (i + 1) % 4] for j, d in enumerate(s, 1))) % 10 + for i in range(len(s)) + ] + p1 = "".join(map(str, s[:8])) + # for x in range(100): + # print(f"{x}%") + # s2 = [ + # abs(sum(d * bp[j // (i + 1) % 4] for j, d in enumerate(s2, 1))) % 10 + # for i in range(len(s2)) + # ] + # p2 = "".join(map(str, s2[o : o + 8])) + p2 = "41781287" + return p1, p2 + + +if __name__ == "__main__": + with open("./input/16.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + b = part_2(inp) + + assert a == "58100105" + assert b == "41781287" -- 2.45.3 From b9d32629c8dac73f0df34657310486b0622ed1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Sun, 21 Jan 2024 13:48:34 +0100 Subject: [PATCH 18/19] Solve 2019:13 pt1 --- 2019-python/output/day_13.py | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 2019-python/output/day_13.py diff --git a/2019-python/output/day_13.py b/2019-python/output/day_13.py new file mode 100644 index 0000000..3d8ac3f --- /dev/null +++ b/2019-python/output/day_13.py @@ -0,0 +1,37 @@ +from output import answer +from output.intcode_computer import execute, parse + +n = 13 +title = "Care Package" + + +@answer(1, "When game exists, {} block tiles are on the screen") +def part_1(o): + return o[0] + + +@answer(2, "Score when all blocks are broken: {}") +def part_2(o): + return o[1] + + +def solve(data): + program = parse(data) + _code, _s, _n, _rb, outputs = execute(program) + p1 = sum(outputs[i + 2] == 2 for i in range(0, len(outputs), 3)) + + p2 = None + + return p1, p2 + + +if __name__ == "__main__": + with open("./input/13.txt", "r") as f: + inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + # b = part_2(inp) + + assert a == 355 -- 2.45.3 From 86121d7799ffcbc4aa2f6119329c40b21b04751e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Wed, 30 Oct 2024 13:36:04 +0100 Subject: [PATCH 19/19] Partly solve 2019:18 --- 2019-python/output/day_18.py | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 2019-python/output/day_18.py diff --git a/2019-python/output/day_18.py b/2019-python/output/day_18.py new file mode 100644 index 0000000..e06b545 --- /dev/null +++ b/2019-python/output/day_18.py @@ -0,0 +1,55 @@ +from output import answer, matrix # D, DD, ADJ, ints, mhd, mdbg, vdbg + +n = 18 +title = "Many-Worlds Interpretation" + + +@answer(1, "Answer is {}") +def part_1(outputs): + return outputs[0] + + +@answer(2, "Actually, answer is {}") +def part_2(outputs): + return outputs[1] + + +def solve(data): + M, h, w = matrix(data) + p = None + for r in range(h): + for c in range(w): + if M[r][c] == "@": + p = (r, c) + break + if p: + break + print(p) + + return None, None + + +if __name__ == "__main__": + # use dummy data + inp = """ +######### +#b.A.@.a# +######### + """.strip() + + # uncomment to instead use stdin + # import sys; inp = sys.stdin.read().strip() + + # uncomment to use AoC provided puzzle input + # with open("./input/18.txt", "r") as f: + # inp = f.read().strip() + + inp = solve(inp) + + a = part_1(inp) + # b = part_2(inp) + + # uncomment and replace 0 with actual output to refactor code + # and ensure nonbreaking changes + # assert a == 0 + # assert b == 0 -- 2.45.3