17_ascii.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. #!/usr/bin/env python3
  2. import sys
  3. from itertools import combinations, islice
  4. from intcode import read_program, run, run_inputs
  5. def read_grid(program):
  6. output = ''.join(map(chr, run(program)))
  7. lines = output.rstrip().split('\n')
  8. def gen_lines():
  9. yield '.' * len(lines[0])
  10. yield from lines
  11. yield '.' * len(lines[0])
  12. return [list('.%s.' % line) for line in gen_lines()]
  13. def intersections(grid):
  14. for y, row in islice(enumerate(grid), 1, len(grid) - 1):
  15. for x, cell in islice(enumerate(row), 1, len(row) - 1):
  16. if row[x - 1:x + 2] == list('###') and \
  17. grid[y - 1][x] == '#' and grid[y + 1][x] == '#':
  18. yield x, y
  19. def calibrate(grid):
  20. return sum((x - 1) * (y - 1) for x, y in intersections(grid))
  21. def find_path(grid):
  22. x, y = next((x, y) for y, row in enumerate(grid)
  23. for x, cell in enumerate(row) if cell in '^v<>')
  24. dx, dy = {'^': (0, -1), 'v': (0, 1), '<': (-1, 0), '>': (1, 0)}[grid[y][x]]
  25. turn = None
  26. while True:
  27. # walk forward
  28. forward = 0
  29. while grid[y + dy][x + dx] == '#':
  30. x += dx
  31. y += dy
  32. forward += 1
  33. if forward > 0:
  34. # when blocked, turn right
  35. if turn:
  36. yield turn
  37. yield str(forward)
  38. turn = 'R'
  39. dx, dy = -dy, dx
  40. elif turn == 'R':
  41. # if right is blocked, turn left instead
  42. turn = 'L'
  43. dx, dy = -dx, -dy
  44. elif turn is None:
  45. turn = 'R'
  46. dx, dy = -dy, dx
  47. else:
  48. # if left is blocked too, we're at the end
  49. break
  50. def subroutines(path):
  51. for l in range(11, 1, -1):
  52. for i in range(len(path) - 2 * l):
  53. subseq = path[i:i + l]
  54. starts = [i]
  55. for j in range(i + l, len(path)):
  56. if path[j:j + l] == subseq:
  57. starts.append(j)
  58. if len(starts) > 1:
  59. yield l, starts
  60. def pick_subroutines(path):
  61. work = set()
  62. for l, starts in subroutines(path):
  63. for use in range(len(starts), 1, -1):
  64. for used_starts in combinations(starts, use):
  65. prio = -l * len(used_starts), -l
  66. work.add((prio, l, used_starts))
  67. # get rid of overlaps
  68. taken = [False] * len(path)
  69. for prio, l, starts in sorted(work):
  70. if any(any(taken[i:i + l]) for i in starts):
  71. continue
  72. for start in starts:
  73. for i in range(start, start + l):
  74. taken[i] = True
  75. start = starts[0]
  76. yield ','.join(path[start:start + l])
  77. def make_subroutines(path):
  78. path = list(path)
  79. subs = {'A': '', 'B': '', 'C': ''}
  80. main = ','.join(path)
  81. for name, sub in zip('ABC', islice(pick_subroutines(path), 3)):
  82. subs[name] = sub
  83. main = main.replace(sub, name)
  84. assert len(main) <= 20
  85. assert all(len(sub) <= 20 for sub in subs.values())
  86. return main, subs
  87. def rescue(program, main, subs):
  88. lines = [main, subs['A'], subs['B'], subs['C'], 'n']
  89. inputs = map(ord, ('\n'.join(lines) + '\n'))
  90. for output in run_inputs(program, inputs):
  91. pass
  92. return output
  93. # part 1
  94. program = read_program(sys.stdin)
  95. grid = read_grid(program)
  96. print(calibrate(grid))
  97. # part 2
  98. path = list(find_path(grid))
  99. program[0] = 2
  100. main, subs = make_subroutines(path)
  101. print(rescue(program, main, subs))