17_ascii.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #!/usr/bin/env python3
  2. import sys
  3. from itertools import combinations, islice
  4. from operator import add, mul, lt, eq
  5. def run(p, get_input, memsize=0):
  6. def decode_param(offset):
  7. return p[pc + offset], modes // (10 ** (offset - 1)) % 10
  8. def pload(offset):
  9. param, mode = decode_param(offset)
  10. return param if mode == 1 else p[param + relbase * mode // 2]
  11. def pstore(offset, value):
  12. param, mode = decode_param(offset)
  13. p[param + relbase * mode // 2] = value
  14. opmap = {1: add, 2: mul, 7: lt, 8: eq}
  15. p = p + [0] * memsize
  16. pc = relbase = 0
  17. while p[pc] != 99:
  18. modes, opcode = divmod(p[pc], 100)
  19. if opcode in (1, 2, 7, 8):
  20. pstore(3, opmap[opcode](pload(1), pload(2)))
  21. pc += 4
  22. elif opcode == 3:
  23. pstore(1, get_input())
  24. pc += 2
  25. elif opcode == 4:
  26. yield pload(1)
  27. pc += 2
  28. elif opcode == 5:
  29. pc = pload(2) if pload(1) else pc + 3
  30. elif opcode == 6:
  31. pc = pload(2) if not pload(1) else pc + 3
  32. elif opcode == 9:
  33. relbase += pload(1)
  34. pc += 2
  35. def read_grid(program):
  36. output = ''.join(map(chr, run(program, None, 10000)))
  37. lines = output.rstrip().split('\n')
  38. def gen_lines():
  39. yield '.' * len(lines[0])
  40. yield from lines
  41. yield '.' * len(lines[0])
  42. return [list('.%s.' % line) for line in gen_lines()]
  43. def intersections(grid):
  44. for y, row in islice(enumerate(grid), 1, len(grid) - 1):
  45. for x, cell in islice(enumerate(row), 1, len(row) - 1):
  46. if row[x - 1:x + 2] == list('###') and \
  47. grid[y - 1][x] == '#' and grid[y + 1][x] == '#':
  48. yield x, y
  49. def calibrate(grid):
  50. return sum((x - 1) * (y - 1) for x, y in intersections(grid))
  51. def find_path(grid):
  52. x, y = next((x, y) for y, row in enumerate(grid)
  53. for x, cell in enumerate(row) if cell in '^v<>')
  54. dx, dy = {'^': (0, -1), 'v': (0, 1), '<': (-1, 0), '>': (1, 0)}[grid[y][x]]
  55. turn = None
  56. while True:
  57. # walk forward
  58. forward = 0
  59. while grid[y + dy][x + dx] == '#':
  60. x += dx
  61. y += dy
  62. forward += 1
  63. if forward > 0:
  64. # when blocked, turn right
  65. if turn:
  66. yield turn
  67. yield str(forward)
  68. turn = 'R'
  69. dx, dy = -dy, dx
  70. elif turn == 'R':
  71. # if right is blocked, turn left instead
  72. turn = 'L'
  73. dx, dy = -dx, -dy
  74. elif turn is None:
  75. turn = 'R'
  76. dx, dy = -dy, dx
  77. else:
  78. # if left is blocked too, we're at the end
  79. break
  80. def subroutines(path):
  81. for l in range(11, 1, -1):
  82. for i in range(len(path) - 2 * l):
  83. subseq = path[i:i + l]
  84. starts = [i]
  85. for j in range(i + l, len(path)):
  86. if path[j:j + l] == subseq:
  87. starts.append(j)
  88. if len(starts) > 1:
  89. yield l, starts
  90. def pick_subroutines(path):
  91. work = set()
  92. for l, starts in subroutines(path):
  93. for use in range(len(starts), 1, -1):
  94. for used_starts in combinations(starts, use):
  95. prio = -l * len(used_starts), -l
  96. work.add((prio, l, used_starts))
  97. # get rid of overlaps
  98. taken = [False] * len(path)
  99. for prio, l, starts in sorted(work):
  100. if any(any(taken[i:i + l]) for i in starts):
  101. continue
  102. for start in starts:
  103. for i in range(start, start + l):
  104. taken[i] = True
  105. start = starts[0]
  106. yield ','.join(path[start:start + l])
  107. def make_subroutines(path):
  108. path = list(path)
  109. subs = {'A': '', 'B': '', 'C': ''}
  110. main = ','.join(path)
  111. for name, sub in zip('ABC', islice(pick_subroutines(path), 3)):
  112. subs[name] = sub
  113. main = main.replace(sub, name)
  114. assert len(main) <= 20
  115. assert all(len(sub) <= 20 for sub in subs.values())
  116. return main, subs
  117. def rescue(program, main, subs, gridlen):
  118. lines = [main, subs['A'], subs['B'], subs['C'], 'n']
  119. inputs = list(map(ord, ('\n'.join(lines) + '\n')[::-1]))
  120. for output in run(program, inputs.pop, 10000):
  121. pass
  122. return output
  123. # part 1
  124. program = list(map(int, sys.stdin.readline().split(',')))
  125. grid = read_grid(program)
  126. print(calibrate(grid))
  127. # part 2
  128. path = list(find_path(grid))
  129. program[0] = 2
  130. main, subs = make_subroutines(path)
  131. print(rescue(program, main, subs, len(grid) * len(grid[0])))