#!/usr/bin/env python3 from logging import debug, DEBUG, basicConfig from sys import argv from re import finditer class Manual: class Line: light_solution: str count_solution: str choices: list[str] def __init__(self, input_line: str): self.light_solution = input_line.split()[0][1:-1] self.count_solution = "".join( input_line.split()[len(input_line.split()) - 1][1:-1] ) self.choices = [x[1:-1] for x in input_line.split()[1:-1]] self.choices.sort(key=lambda x: len(x)) def find_solution(self) -> list[int]: debug("CURRENT CHOICES: %s", self.choices) while self.count_solution != ",".join(["0" for _ in self.light_solution]): # This needs to go back a step if we ever reach an empty best_options list... # And iterate through the best options. # This is probably best done with recursion. debug("CURRENT COUNT: %s", self.count_solution) minimum_value = str( min([int(x) for x in self.count_solution.split(",") if int(x)]) ) minimum_indexes = [ str(i) for i, v in enumerate(self.count_solution.split(",")) if v == minimum_value ] debug(minimum_indexes) best_options = [ len([v.string for v in c]) for c in [ finditer("|".join(minimum_indexes), c) for c in self.choices ] ] debug(best_options) solved_indexes = [ str(x) for x in range(len(self.count_solution.split(","))) if not int(self.count_solution.split(",")[x]) ] debug(solved_indexes) for i, _ in enumerate(best_options): for c in self.choices[i].split(","): if c in solved_indexes: best_options[i] = 0 debug(best_options) current_choice = self.choices[best_options.index(max(best_options))] debug( "CURRENT CHOICE: %s", current_choice, ) for _ in range(int(minimum_value)): for c in current_choice.split(","): temp_count = self.count_solution.split(",") temp_count[int(c)] = str(int(temp_count[int(c)]) - 1) self.count_solution = ",".join(temp_count) return [] lines: list[Line] def __init__(self): self.lines = [] def parse_input( input_filepath: str, ) -> Manual: with open(file=input_filepath, mode="r") as input_file: input_data: list[str] = input_file.readlines() debug(f"\n\nRAW INPUT: {input_data}\n\n") # Not even trying to be legible anymore. # output = [ # [segment[1:-1] for segment in line.strip().split()] for line in input_data # ] output = Manual() output.lines = [Manual.Line(l) for l in input_data] return output def traverse_tree(solution: str, choices: list[str], states: set[str]) -> int: debug("CURRENT STATES: %s", states) if solution in states: debug("FOUND IT") return 0 new_states: set[str] = set() for state in states: for c in choices: temp_state = state for s in range(len(state)): if str(s) in c.split(","): if state[s] == ".": temp_state = temp_state[:s] + "#" + temp_state[s][1:] else: temp_state = temp_state[:s] + "." + temp_state[s][1:] new_states.add(temp_state) return 1 + traverse_tree(solution, choices, new_states) def find_shortest_solutions( manual: list[list[str]], ) -> list[int]: output: list[int] = [] for line in manual: debug( "CURRENT SOLUTION: %s\nCURRENT CHOICES: %s", line[0], [ "".join(["#" if str(x) in button else "." for x in range(len(line[0]))]) for button in line[1:-1] ], ) output.append(traverse_tree(line[0], line[1:-1], {"".join("." * len(line[0]))})) return output def main() -> None: input_filepath = "input/manual.txt" input_manual = parse_input(input_filepath) debug( "MANUAL\n%s", [ [line.light_solution, line.choices, line.count_solution] for line in input_manual.lines ], ) for line in input_manual.lines: line.find_solution() # shortest_solutions = find_shortest_solutions(input_manual) # debug(f"SHORTEST SOLUTIONS: {shortest_solutions}") # print(f"The sum of shortest solution is: {sum(shortest_solutions)}") return if __name__ == "__main__": if "-d" in argv or "--debug" in argv: basicConfig(filename="debug.log", level=DEBUG) main() exit(0)