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
114import intcode
125from 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
2778class 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
0 commit comments