Let's see how far I get this year.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

day03-2.py 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. from helpers import debug, load_input
  2. def is_digit(n):
  3. try:
  4. m = int(n)
  5. except ValueError:
  6. return False
  7. return True
  8. def is_star(n):
  9. return n == "*"
  10. input_numbers = []
  11. stars = []
  12. class InputNumber:
  13. def __init__(self, number, start_pos, end_pos, line):
  14. self.number = number
  15. self.start_pos = start_pos
  16. self.end_pos = end_pos
  17. self.line = line
  18. def _is_symbol(self, char):
  19. return (char != "." and not is_digit(char))
  20. def _check_up(self, input):
  21. if self.line == 0:
  22. return False
  23. cur_line = input[self.line - 1]
  24. for i in range(max(0, self.start_pos-1), min(len(cur_line)-1, self.end_pos+2)):
  25. char = cur_line[i]
  26. if self._is_symbol(char):
  27. debug(f"Found symbol next to {self.number} at line {self.line-1}: {i}")
  28. return True
  29. return False
  30. def _check_down(self, input):
  31. if self.line == len(input)-1:
  32. return False
  33. cur_line = input[self.line + 1]
  34. for i in range(max(0, self.start_pos-1), min(len(cur_line)-1, self.end_pos+2)):
  35. char = cur_line[i]
  36. if self._is_symbol(char):
  37. debug(f"Found symbol next to {self.number} at line {self.line+1}: {i}")
  38. return True
  39. return False
  40. def _check_left(self, input):
  41. if self.start_pos == 0:
  42. debug("Already at the left-hand side of the line")
  43. return False
  44. if self._is_symbol(input[self.line][self.start_pos-1]):
  45. debug(f"Found symbol next to {self.number} at line {self.line}: {self.start_pos-1}")
  46. return True
  47. return False
  48. def _check_right(self, input):
  49. if self.end_pos == len(input[0]) - 1:
  50. debug("Already at the right-hand side of the line")
  51. return False
  52. if self._is_symbol(input[self.line][self.end_pos+1]):
  53. debug(f"Found symbol next to {self.number} at line {self.line}: {self.end_pos+1}")
  54. return True
  55. return False
  56. def is_part_number(self, input):
  57. return (
  58. self._check_up(input)
  59. or self._check_down(input)
  60. or self._check_left(input)
  61. or self._check_right(input)
  62. )
  63. def thats_me(self, line, pos):
  64. return self.line == line and pos in range(self.start_pos, self.end_pos+1)
  65. class Star:
  66. def __init__(self, line, pos):
  67. self.line = line
  68. self.pos = pos
  69. self.adjacent_numbers = []
  70. def _check_up(self, input):
  71. if self.line == 0:
  72. debug("Already at the top")
  73. return False
  74. found_one = False
  75. for i in range(self.pos-1, self.pos+2):
  76. debug(f"Checking {self.line-1}:{i}")
  77. char = input[self.line-1][i]
  78. if is_digit(char):
  79. target_number = [number for number in input_numbers if number.thats_me(self.line-1, i)][0]
  80. if target_number not in self.adjacent_numbers:
  81. self.adjacent_numbers.append(target_number)
  82. found_one = True
  83. return found_one
  84. def _check_down(self, input):
  85. if self.line == len(input) - 1:
  86. debug("Already at the bottom")
  87. return False
  88. found_one = False
  89. for i in range(self.pos-1, self.pos+2):
  90. debug(f"Checking {self.line+1}:{i}")
  91. char = input[self.line+1][i]
  92. if is_digit(char):
  93. target_number = [number for number in input_numbers if number.thats_me(self.line+1, i)][0]
  94. if target_number not in self.adjacent_numbers:
  95. self.adjacent_numbers.append(target_number)
  96. found_one = True
  97. return found_one
  98. def _check_left(self, input):
  99. if self.pos == 0:
  100. debug ("Already at the left side")
  101. return False
  102. debug(f"Checking {self.line}:{self.pos-1}")
  103. char = input[self.line][self.pos-1]
  104. if is_digit(char):
  105. target_number = [number for number in input_numbers if number.thats_me(self.line, self.pos-1)][0]
  106. if target_number not in self.adjacent_numbers:
  107. self.adjacent_numbers.append(target_number)
  108. return True
  109. return False
  110. def _check_right(self, input):
  111. if self.pos == len(input[0]) - 1:
  112. debug ("Already at the right side")
  113. return False
  114. debug(f"Checking {self.line}:{self.pos+1}")
  115. char = input[self.line][self.pos+1]
  116. if is_digit(char):
  117. target_number = [number for number in input_numbers if number.thats_me(self.line, self.pos+1)][0]
  118. if target_number not in self.adjacent_numbers:
  119. self.adjacent_numbers.append(target_number)
  120. return True
  121. return False
  122. def _is_gear(self, input):
  123. self._check_up(input)
  124. self._check_down(input)
  125. self._check_left(input)
  126. self._check_right(input)
  127. l_adj = len(self.adjacent_numbers)
  128. n_adj = [number.number for number in self.adjacent_numbers]
  129. debug(f"Star at {self.line}:{self.pos} has {l_adj} adjacent number{'s' if l_adj > 1 else ''}: {n_adj}")
  130. return len(self.adjacent_numbers) == 2
  131. def gear_value(self, input):
  132. if self._is_gear(input):
  133. return self.adjacent_numbers[0].number * self.adjacent_numbers[1].number
  134. return 0
  135. def main():
  136. lines = load_input(3)
  137. # Input is lines of periods (.), numbers (0-9), and symbols
  138. # (anything that isn't a period or number)
  139. # Any asterisk (*) adjacent to exactly two numbers is a gear;
  140. # its value is the product of the two numbers.
  141. # Get the sum of all of the gear values.
  142. max_len = len(lines[0]) # all lines are the same length
  143. for j, line in enumerate(lines):
  144. i = 0
  145. while i < max_len:
  146. if is_star(line[i]):
  147. star = Star(
  148. line=j,
  149. pos=i
  150. )
  151. stars.append(star)
  152. # debug(f"Found a star at {star.line}:{star.pos}")
  153. elif is_digit(line[i]):
  154. current_number = ""
  155. start_pos = i
  156. while i < max_len and is_digit(line[i]):
  157. current_number += f"{line[i]}"
  158. i += 1
  159. if i < max_len and is_star(line[i]):
  160. star = Star(
  161. line=j,
  162. pos=i
  163. )
  164. stars.append(star)
  165. # debug(f"Found a star next to a number at {star.line}:{star.pos}")
  166. end_pos = i
  167. input_number = InputNumber(
  168. number = int(current_number),
  169. start_pos = start_pos,
  170. end_pos = end_pos-1, # i is one greater than the end of the number
  171. line = j
  172. )
  173. input_numbers.append(input_number)
  174. # debug(f"Found {input_number.number} at line {input_number.line} ({input_number.start_pos}, {input_number.end_pos})")
  175. i += 1
  176. # Now I have a list of the positions of all the numbers and stars in the input.
  177. gear_values = []
  178. for s in stars:
  179. gear_values.append(s.gear_value(lines))
  180. print(sum(gear_values))
  181. if __name__ == "__main__":
  182. main()