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.4KB

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