Skip to content

Commit 4cd863a

Browse files
committed
Advent of Code (Python): tidy 2019/17
1 parent 4d146a2 commit 4cd863a

2 files changed

Lines changed: 89 additions & 95 deletions

File tree

advent_of_code/2019/d17.py

Lines changed: 85 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,94 @@
11
#!/bin/python
22
"""Advent of Code, Day 17: Set and Forget."""
3-
from __future__ import annotations
4-
5-
import collections
6-
import functools
7-
import itertools
8-
import math
9-
import re
10-
3+
import typing
114
import intcode
125
from lib import aoc
136

14-
SAMPLE = """\
15-
..#..........
16-
..#..........
17-
#######...###
18-
#.#...#...#.#
19-
#############
20-
..#...#...#..
21-
..#####...^.."""
227

23-
LineType = int
24-
InputType = list[LineType]
8+
def trace_path(coords: dict[str, set[complex]]) -> list[str]:
9+
"""Walk the scaffolding, translating the path to a series of L|R turns and forward steps."""
10+
scaffolding = coords["#"]
11+
for char, delta in aoc.ARROW_DIRECTIONS.items():
12+
if char in coords:
13+
direction = delta
14+
pos = coords[char].pop()
15+
break
16+
steps = []
17+
while pos + direction * 1j in scaffolding or pos + direction * -1j in scaffolding:
18+
if pos + direction * 1j in scaffolding:
19+
steps.append("R")
20+
direction *= 1j
21+
else:
22+
steps.append("L")
23+
direction *= -1j
24+
count = 0
25+
while pos + direction in scaffolding:
26+
pos += direction
27+
count += 1
28+
steps.append(str(count))
29+
return steps
30+
31+
32+
def compute_subroutines(steps: list[str]) -> dict[str, list[str]]:
33+
"""Return three subroutines that can be used to compose the steps."""
34+
segments = [steps]
35+
subroutines = {}
36+
for routine in "ABC":
37+
for size in range(len(segments[0]), 0, -2):
38+
# Find the largest block of steps that repeats at least three times.
39+
block = segments[0][:size]
40+
count = sum(
41+
block == segment[i:i + size]
42+
for segment in segments
43+
for i in range(len(segment) - size + 1)
44+
)
45+
if count > 2:
46+
# Remove the subroutine and compute the remaining segments.
47+
subroutines[routine] = block
48+
new_segments = []
49+
for segment in segments:
50+
idx = 0
51+
while idx < len(segment):
52+
start = idx
53+
while idx < len(segment) and segment[idx:idx + size] != block:
54+
idx += 2
55+
if start != idx:
56+
new_segments.append(segment[start:idx])
57+
idx += size
58+
segments = new_segments
59+
break
60+
61+
assert not segments, "There should be no remaining segments."
62+
return subroutines
63+
64+
65+
def compute_program(steps: list[str], subroutines: dict[str, list[str]]) -> list[str]:
66+
"""Translate a series of steps into subroutines."""
67+
idx = 0
68+
program = []
69+
while idx < len(steps):
70+
for name, sub in subroutines.items():
71+
if steps[idx:idx + len(sub)] == sub:
72+
program.append(name)
73+
idx += len(sub)
74+
break
75+
return program
2576

2677

2778
class Day17(aoc.Challenge):
2879
"""Day 17: Set and Forget."""
2980

30-
DEBUG = False
31-
# SUBMIT = {1: False, 2: False}
32-
3381
TESTS = [
34-
aoc.TestCase(inputs=SAMPLE, part=1, want=aoc.TEST_SKIP),
35-
aoc.TestCase(inputs=SAMPLE, part=2, want=0),
82+
aoc.TestCase(inputs="", part=1, want=aoc.TEST_SKIP),
83+
aoc.TestCase(inputs="", part=2, want=aoc.TEST_SKIP),
3684
]
3785
INPUT_PARSER = aoc.parse_one_str
3886

39-
def solver(self, puzzle_input: InputType, part_one: bool) -> int | str:
40-
computer = intcode.Computer(puzzle_input, debug=self.DEBUG)
87+
def solver(self, puzzle_input: str, part_one: bool) -> int:
88+
computer = intcode.Computer(puzzle_input)
89+
# Run the computer, read the output map, parse it.
4190
computer.run()
4291
data = [chr(i) for i in computer.output]
43-
# print("".join(data))
4492
display = aoc.CoordinatesParserC(chars="#<>^v").parse("".join(data))
4593
scaffolding = display.coords["#"]
4694
if part_one:
@@ -49,80 +97,22 @@ def solver(self, puzzle_input: InputType, part_one: bool) -> int | str:
4997
for pos in scaffolding
5098
if all(pos + direction in scaffolding for direction in aoc.FOUR_DIRECTIONS)
5199
)
52-
for char, delta in aoc.ARROW_DIRECTIONS.items():
53-
if char in display.coords:
54-
direction = delta
55-
pos = display.coords[char].pop()
56-
break
57-
# print(pos, direction)
58-
steps = []
59-
while pos + direction * 1j in scaffolding or pos + direction * -1j in scaffolding:
60-
if pos + direction * 1j in scaffolding:
61-
steps.append("R")
62-
direction *= 1j
63-
else:
64-
steps.append("L")
65-
direction *= -1j
66-
count = 0
67-
while pos + direction in scaffolding:
68-
pos += direction
69-
count += 1
70-
steps.append(str(count))
71-
segments = [steps]
72-
subprograms = {}
73-
for program in "ABC":
74-
for size in range(len(segments[0]), 0, -1):
75-
if size % 2:
76-
continue
77-
block = segments[0][:size]
78-
count = sum(block == segment[i:i + size] for segment in segments for i in range(len(segment) - size + 1))
79-
if count > 2:
80-
# print(block, size, count)
81-
subprograms[program] = block
82-
new_segments = []
83-
for segment in segments:
84-
idx = 0
85-
while idx < len(segment):
86-
start = idx
87-
while idx < len(segment) and segment[idx:idx + size] != block:
88-
idx += 2
89-
if start != idx:
90-
new_segments.append(segment[start:idx])
91-
idx += size
92-
segments = new_segments
93-
# print(segments)
94-
break
95-
96-
assert not segments
97-
print(subprograms)
100+
# Convert the path to a series of L|R turns and steps forward.
101+
steps = trace_path(typing.cast(dict[str, set[complex]], display.coords))
102+
# Find repeating patterns/subroutines in the data.
103+
subroutines = compute_subroutines(steps)
104+
# Translate the steps into a series of subroutines.
105+
program = compute_program(steps, subroutines)
98106

99-
idx = 0
100-
program = []
101-
while idx < len(steps):
102-
for name, sub in subprograms.items():
103-
if steps[idx:idx + len(sub)] == sub:
104-
program.append(name)
105-
idx += len(sub)
106-
break
107-
print(program)
107+
# Write the ASCII program and subroutines into the computer and run it.
108108
computer.reset()
109109
computer.memory[0] = 2
110-
write = ",".join(program) + "\n"
110+
computer.input_line(",".join(program))
111111
for name in "ABC":
112-
write += ",".join(subprograms[name]) + "\n"
113-
write += "n\n"
114-
computer.input.extend([ord(i) for i in write])
112+
computer.input_line(",".join(subroutines[name]))
113+
computer.input_line("n")
115114
computer.run()
115+
# Read the result.
116116
return computer.output.pop()
117-
118-
computer.run()
119-
120-
# <A>,<A>,<B>,<C>,<C>,<A>,<B>,<C>,<A>,<B>
121-
# :%s/L,10,R,8,R,12/<C>
122-
# :%s/L,8,L,8,R,12,L,8,L,8/<B>
123-
# :%s/L,12,L,12,R,12/<A>
124-
125-
126-
127117

128118
# vim:expandtab:sw=4:ts=4

advent_of_code/2019/intcode.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ def _debug(self, msg: str) -> None:
105105
if self.debug:
106106
print(msg)
107107

108+
def input_line(self, line: str) -> None:
109+
"""Write a line of ASCII to the input."""
110+
self.input.extend(ord(i) for i in line + "\n")
111+
108112
def get_input(self) -> int | None:
109113
"""Read input or change state to blocked."""
110114
if self.input:

0 commit comments

Comments
 (0)