123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- from helpers import debug, load_input
-
- def is_digit(n):
- try:
- m = int(n)
- except ValueError:
- return False
- return True
-
- def is_star(n):
- return n == "*"
-
- input_numbers = []
- stars = []
-
- class InputNumber:
- def __init__(self, number, start_pos, end_pos, line):
- self.number = number
- self.start_pos = start_pos
- self.end_pos = end_pos
- self.line = line
-
- def _is_symbol(self, char):
- return (char != "." and not is_digit(char))
-
- def _check_up(self, input):
- if self.line == 0:
- return False
- cur_line = input[self.line - 1]
- for i in range(max(0, self.start_pos-1), min(len(cur_line)-1, self.end_pos+2)):
- char = cur_line[i]
- if self._is_symbol(char):
- debug(f"Found symbol next to {self.number} at line {self.line-1}: {i}")
- return True
- return False
-
- def _check_down(self, input):
- if self.line == len(input)-1:
- return False
- cur_line = input[self.line + 1]
- for i in range(max(0, self.start_pos-1), min(len(cur_line)-1, self.end_pos+2)):
- char = cur_line[i]
- if self._is_symbol(char):
- debug(f"Found symbol next to {self.number} at line {self.line+1}: {i}")
- return True
- return False
-
- def _check_left(self, input):
- if self.start_pos == 0:
- debug("Already at the left-hand side of the line")
- return False
- if self._is_symbol(input[self.line][self.start_pos-1]):
- debug(f"Found symbol next to {self.number} at line {self.line}: {self.start_pos-1}")
- return True
- return False
-
- def _check_right(self, input):
- if self.end_pos == len(input[0]) - 1:
- debug("Already at the right-hand side of the line")
- return False
- if self._is_symbol(input[self.line][self.end_pos+1]):
- debug(f"Found symbol next to {self.number} at line {self.line}: {self.end_pos+1}")
- return True
- return False
-
- def is_part_number(self, input):
- return (
- self._check_up(input)
- or self._check_down(input)
- or self._check_left(input)
- or self._check_right(input)
- )
-
- def thats_me(self, line, pos):
- return self.line == line and pos in range(self.start_pos, self.end_pos+1)
-
- class Star:
- def __init__(self, line, pos):
- self.line = line
- self.pos = pos
- self.adjacent_numbers = []
-
- def _check_up(self, input):
- if self.line == 0:
- debug("Already at the top")
- return False
- found_one = False
- for i in range(self.pos-1, self.pos+2):
- debug(f"Checking {self.line-1}:{i}")
- char = input[self.line-1][i]
- if is_digit(char):
- target_number = [number for number in input_numbers if number.thats_me(self.line-1, i)][0]
- if target_number not in self.adjacent_numbers:
- self.adjacent_numbers.append(target_number)
- found_one = True
- return found_one
-
- def _check_down(self, input):
- if self.line == len(input) - 1:
- debug("Already at the bottom")
- return False
- found_one = False
- for i in range(self.pos-1, self.pos+2):
- debug(f"Checking {self.line+1}:{i}")
- char = input[self.line+1][i]
- if is_digit(char):
- target_number = [number for number in input_numbers if number.thats_me(self.line+1, i)][0]
- if target_number not in self.adjacent_numbers:
- self.adjacent_numbers.append(target_number)
- found_one = True
- return found_one
-
- def _check_left(self, input):
- if self.pos == 0:
- debug ("Already at the left side")
- return False
- debug(f"Checking {self.line}:{self.pos-1}")
- char = input[self.line][self.pos-1]
- if is_digit(char):
- target_number = [number for number in input_numbers if number.thats_me(self.line, self.pos-1)][0]
- if target_number not in self.adjacent_numbers:
- self.adjacent_numbers.append(target_number)
- return True
- return False
-
- def _check_right(self, input):
- if self.pos == len(input[0]) - 1:
- debug ("Already at the right side")
- return False
- debug(f"Checking {self.line}:{self.pos+1}")
- char = input[self.line][self.pos+1]
- if is_digit(char):
- target_number = [number for number in input_numbers if number.thats_me(self.line, self.pos+1)][0]
- if target_number not in self.adjacent_numbers:
- self.adjacent_numbers.append(target_number)
- return True
- return False
-
- def _is_gear(self, input):
- self._check_up(input)
- self._check_down(input)
- self._check_left(input)
- self._check_right(input)
- l_adj = len(self.adjacent_numbers)
- n_adj = [number.number for number in self.adjacent_numbers]
- debug(f"Star at {self.line}:{self.pos} has {l_adj} adjacent number{'s' if l_adj > 1 else ''}: {n_adj}")
- return len(self.adjacent_numbers) == 2
-
- def gear_value(self, input):
- if self._is_gear(input):
- return self.adjacent_numbers[0].number * self.adjacent_numbers[1].number
- return 0
-
- def main():
- lines = load_input(3)
- # Input is lines of periods (.), numbers (0-9), and symbols
- # (anything that isn't a period or number)
- # Any asterisk (*) adjacent to exactly two numbers is a gear;
- # its value is the product of the two numbers.
- # Get the sum of all of the gear values.
- max_len = len(lines[0]) # all lines are the same length
- for j, line in enumerate(lines):
- i = 0
- while i < max_len:
- if is_star(line[i]):
- star = Star(
- line=j,
- pos=i
- )
- stars.append(star)
- # debug(f"Found a star at {star.line}:{star.pos}")
- elif is_digit(line[i]):
- current_number = ""
- start_pos = i
- while i < max_len and is_digit(line[i]):
- current_number += f"{line[i]}"
- i += 1
- if i < max_len and is_star(line[i]):
- star = Star(
- line=j,
- pos=i
- )
- stars.append(star)
- # debug(f"Found a star next to a number at {star.line}:{star.pos}")
- end_pos = i
- input_number = InputNumber(
- number = int(current_number),
- start_pos = start_pos,
- end_pos = end_pos-1, # i is one greater than the end of the number
- line = j
- )
- input_numbers.append(input_number)
- # debug(f"Found {input_number.number} at line {input_number.line} ({input_number.start_pos}, {input_number.end_pos})")
- i += 1
- # Now I have a list of the positions of all the numbers and stars in the input.
- gear_values = []
- for s in stars:
- gear_values.append(s.gear_value(lines))
-
- print(sum(gear_values))
-
- if __name__ == "__main__":
- main()
|