def solve( self, node_set: 'NodeSetExtended', target: Point2D = Point2D.ZERO_POINT, debugger: Debugger = Debugger(enabled=False), ) -> List['NodeSetExtended']: stack = [node_set] previous_map: PreviousMap = {node_set: None} duplicate_count = 0 debugger.reset() target_position_map = self.get_target_positions(node_set) debugger.reset() while debugger.step_if(stack): current_node_set = stack.pop(0) next_states = self.get_next_states( current_node_set, target_position_map) for next_node_set in next_states: if next_node_set in previous_map: duplicate_count += 1 continue previous_map[next_node_set] = current_node_set if next_node_set.position == target: return self.get_solution(next_node_set, previous_map) stack.append(next_node_set) if debugger.should_report(): debugger.default_report( f"stack: {len(stack)}, pruned: {duplicate_count}") raise Exception(f"Could not find solution")
def add_rows(self, count: int, debugger: Debugger = Debugger(enabled=False)): """ >>> print("!" + Minefield.from_rows_text('..^^.').add_rows(2).show()) !..^^. .^^^^ ^^..^ >>> print("!" + Minefield.from_rows_text( ... '.^^.^.^^^^').add_rows(9).show()) !.^^.^.^^^^ ^^^...^..^ ^.^^.^.^^. ..^^...^^^ .^^^^.^^.^ ^^..^.^^.. ^^^^..^^^. ^..^^^^.^^ .^^^..^.^^ ^^.^^^..^^ """ debugger.reset() total_target = len(self.rows) + count for _ in range(count): self.add_row() debugger.step() if debugger.should_report(): debugger.default_report( f"rows: {len(self.rows)}/{total_target}") return self
def get_index_at_position_after_shuffle_many( self, index, size, count, debugger: Debugger = Debugger(enabled=False), ): total_index = index shuffles = [ shuffle.make_get_index_at_position_after_shuffle(size) for shuffle in reversed(self.shuffles) ] for step in debugger.stepping(range(count)): for shuffle in shuffles: total_index = shuffle(total_index) if total_index == index: break debugger.default_report_if( f"Looking {int(step / count * 10000) / 100}% " f"(current is {total_index})...") else: return total_index cycle_length = step + 1 debugger.default_report(f"Found cycle of length {cycle_length}") for _ in debugger.stepping(range(count % cycle_length)): for shuffle in shuffles: total_index = shuffle(total_index) debugger.default_report_if( f"Finishing after cycle (current is {total_index})...") return total_index
def __mul__( self, count: int, debugger: Debugger = Debugger(enabled=False), ) -> "ModuloShuffle": cls = type(self) if count < 0: raise Exception(f"Cannot calculate negative count shuffle") # noinspection PyArgumentList total = cls( factor=1, offset=0, size=self.size, ) power_shuffle = self power = 1 remaining_count = count debugger.default_report( f"Remaining {math.ceil(math.log2(remaining_count))} rounds " f"({remaining_count}, {bin(remaining_count)})") while debugger.step_if(remaining_count): debugger.default_report_if( f"Remaining {math.ceil(math.log2(remaining_count))} rounds " f"({remaining_count}, {bin(remaining_count)})") if remaining_count % 2: total = total + power_shuffle remaining_count //= 2 power *= 2 power_shuffle = power_shuffle + power_shuffle return total
def get_nth_phase_message( initial: Union[str, Iterable[int]], length: Optional[int] = None, count: int = 100, message_offset: int = 0, debugger: Debugger = Debugger(enabled=False), ) -> str: """ >>> get_nth_phase_message('12345678', count=4) '01029498' >>> get_nth_phase_message('80871224585914546619083218645595') '24176176' >>> get_nth_phase_message('19617804207202209144916044189917') '73745418' >>> get_nth_phase_message('69317163492948606335995924319873') '52432133' """ if length is None: if not hasattr(initial, '__str__'): raise Exception( f"Initial has no length capability, and no length was passed") length = len(initial) if isinstance(initial, str): initial = list(map(int, initial)) result = np.array(initial) # phase_patterns = list(map(list, get_phase_patterns(length))) debugger.default_report(f"Doing {count} iterations") for iteration in debugger.stepping(range(1, count + 1)): result = get_next_phase(result, length, debugger=debugger) debugger.default_report_if(f"Done {iteration}/{count}") return "".join(map(str, result[message_offset:message_offset + 8]))
def find(self, debugger: Debugger = Debugger(enabled=False)) -> Paths: def reporting_format(_: Debugger, message: str) -> str: return ( f"{message} ({len(paths)} found, {len(seen)} seen, " f"{len(stack)} in stack)" ) paths = [] stack: List[CaveFinderStateT] = [self.get_state_class().make_initial()] seen = {stack[0]} with debugger.adding_extra_report_format(reporting_format): debugger.default_report("Looking...") while debugger.step_if(stack): debugger.default_report_if("Looking...") state = stack.pop(0) for next_state in state.get_next_states(self.system.graph): if next_state in seen: continue seen.add(next_state) if next_state.is_terminal: paths.append(next_state.path) continue stack.append(next_state) debugger.default_report("Finished looking") return paths
def find_minimum_cost_to_organise( self, debugger: Debugger = Debugger(enabled=False), ) -> int: """ >>> Maze.from_maze_text(''' ... ############# ... #...........# ... ###B#C#B#D### ... #A#D#C#A# ... ######### ... ''').find_minimum_cost_to_organise() 12521 """ stack = [(0, 0, self)] seen_cost = {self: 0} min_cost: Optional[int] = None while debugger.step_if(stack): move_count, cost, maze = stack.pop(0) if seen_cost[maze] < cost: continue next_move_count = move_count + 1 next_stack = [] for move_cost, next_maze in maze.get_next_moves(): next_cost = cost + move_cost if seen_cost.get(next_maze, next_cost + 1) <= next_cost: continue if min_cost is not None and min_cost <= next_cost: continue next_stack.append((next_move_count, next_cost, next_maze)) if not next_stack: continue max_next_finish_count = max( next_maze.finish_count for _, _, next_maze in next_stack ) for next_move_count, next_cost, next_maze in next_stack: if next_maze.finish_count < max_next_finish_count: continue if next_maze.finished: min_cost = next_cost continue stack.append((next_move_count, next_cost, next_maze)) seen_cost[next_maze] = next_cost if debugger.should_report(): debugger.default_report( f"{len(stack)} in stack, seen {len(seen_cost)}, " f"last move count is {move_count}, last cost is {cost}, " f"min cost is {min_cost}, last maze is:\n{maze}\n" ) debugger.default_report( f"{len(stack)} in stack, seen {len(seen_cost)}, " f"min cost is {min_cost}" ) if min_cost is None: raise Exception(f"Could not find end state") return min_cost
def step_many( self, algorithm: IEAlgorithm, count: int, debugger: Debugger = Debugger(enabled=False), ) -> "Image": """ >>> _algorithm = IEAlgorithm.from_mapping_text( ... "..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#...." ... "#..#..##..###..######.###...####..#..#####..##..#.#####...##.#" ... ".#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..####" ... "#.....#.#....###..#.##......#.....#..#..#..##..#...##.######.#" ... "###.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.#" ... "#..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#...." ... ".####.#..#..#.##.#....##..#.####....##...##..#...#......#.#..." ... "....#.......##..####..#...#.#.#...##..#.#..###..#####........#" ... "..####......#..#" ... ) >>> image = Image.from_image_text(''' ... #..#. ... #.... ... ##..# ... ..#.. ... ..### ... ''') >>> print(":", image.step_many(_algorithm, 1)) : ......... ..##.##.. .#..#.#.. .##.#..#. .####..#. ..#..##.. ...##..#. ....#.#.. ......... >>> print(":", image.step_many(_algorithm, 2)) : ........... ........#.. ..#..#.#... .#.#...###. .#...##.#.. .#.....#.#. ..#.#####.. ...#.#####. ....##.##.. .....###... ........... """ result = self debugger.default_report(f"Stepping {count} times") for step in debugger.stepping(range(count)): result = result.step(algorithm) debugger.default_report_if( f"Stepped {step}/{count} times, {result.light_pixel_count} " f"lights in result, size of {tuple(result.boundary.size)}" ) return result
def test_adding_extra_report_format_multiple(self): with self.assert_output("Message: [<(Hello)>], extra: extra\n"): debugger = Debugger() with debugger.adding_extra_report_format( self.wrapping_debugger_format): with debugger.adding_extra_report_format( self.wrapping_debugger_format_2): with debugger.adding_extra_report_format( self.wrapping_debugger_format_3): debugger.default_report("Hello")
def step_many(self, step_count: Optional[int] = None, debugger: Debugger = Debugger(enabled=False)): if step_count is None: steps = count() else: steps = range(step_count) debugger.reset() for _ in debugger.stepping(steps): self.step() if self.has_finished(): break if debugger.should_report(): debugger.default_report( f"remaining: {len(self.get_remaining_positions())}, " f"position: {self.position}") return self
def step_many( self, sequence: str, step_count: int = 40, debugger: Debugger = Debugger(enabled=False)) -> str: """ >>> LookAndSay().step_many("1", 5) '312211' """ debugger.reset() result = sequence for step in debugger.stepping(range(step_count)): result = self.step(result) if debugger.should_report(): debugger.default_report(f"step {step}/{step_count}") return result
def find_min_mana_necessary( self, debugger: Debugger = Debugger(enabled=False), ) -> int: def reporting_format(_: Debugger, message: str) -> str: if min_mana_spent is None: min_mana_spent_str = "no winner yet" else: min_mana_spent_str = f"best is {min_mana_spent}" return f"{message} ({min_mana_spent_str}, {len(stack)} in stack)" with debugger.adding_extra_report_format(reporting_format): stack = [deepcopy(self)] min_mana_spent = None min_mana_game = None debugger.default_report_if("Searching for a winning game...") while debugger.step_if(stack): debugger.default_report_if("Searching for a winning game...") game = stack.pop(0) if min_mana_spent is not None \ and game.player.mana_spent >= min_mana_spent: continue next_games = game.get_next_games(debugger) for next_game in next_games: if (min_mana_spent is not None and next_game.player.mana_spent >= min_mana_spent): continue if next_game.winner == CharacterEnum.Player: min_mana_spent = next_game.player.mana_spent min_mana_game = next_game debugger.report(f"Better game found: {min_mana_spent}") stack.append(next_game) debugger.default_report(f"Finished searching") if min_mana_spent is None: raise Exception(f"Could not find a winning game") options_played_str = ', '.join( option.name for option in min_mana_game.options_played if isinstance(option, SpellEnum)) debugger.report(f"Min mana game moves: {options_played_str}") return min_mana_spent
def get_minimum_step_count( self, start: int = 0, return_to_start: bool = False, debugger: Debugger = Debugger(enabled=False), ) -> int: """ >>> Graph({ ... (0, 1): 2, (1, 0): 2, ... (0, 4): 2, (4, 0): 2, ... (1, 2): 6, (2, 1): 6, ... (2, 3): 2, (3, 2): 2, ... (3, 4): 8, (4, 3): 8, ... }).get_minimum_step_count() 14 """ nodes = self.get_nodes() if start not in nodes: raise Exception(f"Start {start} is not in nodes {nodes}") other_nodes = set(nodes) - {start} prefix = (start, ) if return_to_start: suffix = prefix else: suffix = () visit_orders = (prefix + permutation + suffix for permutation in itertools.permutations(other_nodes)) min_distance = None trip_distances_cache = {} for visit_order in debugger.stepping(visit_orders): distance = sum( self.get_shortest_distance( node_a, node_b, nodes, trip_distances_cache=trip_distances_cache) for node_a, node_b in get_windows(visit_order, 2)) if min_distance is None or distance < min_distance: min_distance = distance if debugger.should_report(): debugger.default_report(f"min distance: {min_distance}") return min_distance
def apply_extended( self, state: Optional[StateExtended] = None, debugger: Debugger = Debugger(enabled=False), ) -> StateExtended: """ >>> def check(instructions_text, state_values=None, program_counter=0): ... _state = InstructionSetExtended\\ ... .from_instructions_text(instructions_text)\\ ... .apply_extended(StateExtended( ... state_values or {}, program_counter)) ... # noinspection PyUnresolvedReferences ... values = { ... name: value ... for name, value in _state.values.items() ... if value ... } ... return values, _state.program_counter >>> check( ... "cpy 2 a\\n" ... "tgl a\\n" ... "tgl a\\n" ... "tgl a\\n" ... "cpy 1 a\\n" ... "dec a\\n" ... "dec a\\n" ... ) ({'a': 3}, 7) """ if state is None: state = StateExtended() state.instructions = list(self.instructions) debugger.reset() while debugger.step_if( 0 <= state.program_counter < len(state.instructions)): self.step(state) if debugger.should_report(): debugger.default_report( f"values: {state.values}, pc: {state.program_counter}") return state
def get_message_after_index( initial: str, debugger: Debugger = Debugger(enabled=False), ) -> str: """ >>> get_message_after_index("03036732577212944063491565474664") 84462026 """ start_index = int(initial[:7]) original_length = len(initial) length = original_length * 10000 skip_lengths_count = math.floor(start_index / original_length) lengths_count = int(length / original_length - skip_lengths_count) skipped_phase = list(map(int, initial)) * lengths_count skipped_offset = start_index - skip_lengths_count * original_length skipped_phase = skipped_phase[skipped_offset:] result = skipped_phase debugger.default_report(f"Looking for message skipping {start_index}") for _ in debugger.stepping(range(100)): result = get_next_phase_after_index(result, start_index) debugger.default_report_if(f"So far: {''.join(map(str, result[:8]))}") return "".join(map(str, result[:8]))
def get_all_possible_chains( self, start: str, prune: Optional[Callable[[str], bool]] = None, debugger: Debugger = Debugger(enabled=False), ) -> Iterable[Tuple[str, ...]]: """ >>> list(MachineExtended().get_all_possible_chains("")) [] >>> list(MachineExtended({'H': ['O']}).get_all_possible_chains("HH")) [('HH', 'OH'), ('HH', 'HO'), ('HH', 'OH', 'OO'), ('HH', 'HO', 'OO')] >>> list(MachineExtended({'H': ['O']}).get_all_possible_chains( ... "HH", lambda step: step != 'HO')) [('HH', 'OH'), ('HH', 'OH', 'OO')] >>> list(MachineExtended({'H': ['OH']}).get_all_possible_chains( ... "HH", lambda step: len(step) <= 4)) [('HH', 'OHH'), ('HH', 'HOH'), ('HH', 'OHH', 'OOHH'), ('HH', 'OHH', 'OHOH'), ('HH', 'HOH', 'OHOH'), ('HH', 'HOH', 'HOOH')] >>> list(MachineExtended({'H': ['O'], 'O': ['H']}) ... .get_all_possible_chains("HH")) [('HH', 'OH'), ('HH', 'HO'), ('HH', 'OH', 'OO'), ('HH', 'HO', 'OO'), ('HH', 'OH', 'OO', 'HO'), ('HH', 'HO', 'OO', 'OH')] """ stack = [(start, ())] debugger.reset() while stack: current, chain = stack.pop() next_chain = chain + (current, ) for next_step in self.get_all_possible_next_steps(current): if next_step in next_chain: continue if prune and not prune(next_step): continue yield next_chain + (next_step, ) stack.append((next_step, next_chain)) if debugger.should_report(): debugger.default_report(f"stack size: {len(stack)}, " f"end length: {len(next_chain[-1])}, " f"chain length: {len(next_chain)}")
def get_min_house_number_with_at_least_present_count( self, min_present_count: int, debugger: Debugger = Debugger(enabled=False) ) -> int: """ >>> Santa().get_min_house_number_with_at_least_present_count(100) 6 """ max_present_count_seen = None debugger.reset() for house_number in debugger.stepping(count(1)): present_count = self.get_house_present_count(house_number) if present_count >= min_present_count: return house_number if max_present_count_seen is None \ or present_count > max_present_count_seen: max_present_count_seen = present_count if debugger.should_report(): debugger.default_report( f"max presents: {max_present_count_seen}" f"/{min_present_count}")
def find_scanners_positions( self, debugger: Debugger = Debugger(enabled=False), ) -> List[Tuple[BeaconT, ScannerT]]: """ >>> # noinspection PyUnresolvedReferences >>> [_position for _position, _ in Scanner2DSet.from_scanners_text(''' ... --- scanner 0 --- ... 0,2 ... 4,1 ... 3,3 ... ... --- scanner 1 --- ... -1,-1 ... -5,0 ... -2,1 ... ''').find_scanners_positions()] [Beacon2D(x=0, y=0), Beacon2D(x=5, y=2)] """ if not self.scanners: return [] beacon_class = self.get_beacon_class() first_scanner = self.scanners[0] positions_and_scanners_by_scanner: Dict[int, Tuple[BeaconT, ScannerT]] \ = { id(first_scanner): (beacon_class.get_zero_point(), first_scanner) } found_scanners = [first_scanner] remaining_scanners = self.scanners[1:] debugger.default_report( f"Looking for {len(self.scanners) - 1} positions, found " f"{len(found_scanners) - 1}") while remaining_scanners: for other_index, other in enumerate(remaining_scanners): for found_index, found_scanner \ in debugger.step_if(enumerate(found_scanners)): debugger.default_report_if( f"Looking for {len(self.scanners) - 1} positions, " f"found {len(found_scanners) - 1}") found_position, found_reoriented = \ positions_and_scanners_by_scanner[id(found_scanner)] position_and_scanner = \ found_reoriented.find_other_scanner_position( other, self.min_overlap, ) if not position_and_scanner: continue position, reoriented = position_and_scanner position = position.offset(found_position) found_scanners.append(other) positions_and_scanners_by_scanner[id(other)] = \ position, reoriented remaining_scanners.remove(other) break else: continue break else: raise Exception(f"Could not find any remaining scanner " f"({len(remaining_scanners)} remaining out of " f"{len(self.scanners)})") return [ positions_and_scanners_by_scanner[id(scanner)] for scanner in self.scanners ]