def move_until_stop( self, debugger: Debugger = Debugger(enabled=False), ) -> Tuple[int, "Herd"]: """ >>> _step_count, final = Herd.from_herd_text(''' ... v...>>.vv> ... .vv>>.vv.. ... >>.>v>...v ... >>v>>.>.v. ... v>v.vv.v.. ... >.>>..v... ... .vv..>.>v. ... v.v..>>v.v ... ....v..v.> ... ''').move_until_stop() >>> _step_count 58 >>> print(final) :.>>v>vv.. ..v.>>vv.. ..>>v>>vv. ..>>>>>vv. v......>vv v>v....>>v vvv.....>> >vv......> .>v.vv.v.. """ moved = self for step_count in debugger.stepping(count(1)): previous = moved moved = moved.move() debugger.default_report_if("Searching...") if moved == previous: return step_count, previous
def find_first_synchronised_flash_step_index( self, debugger: Debugger = Debugger(enabled=False), ) -> int: """ >>> GridExtended.from_grid_text(''' ... 5483143223 ... 2745854711 ... 5264556173 ... 6141336146 ... 6357385478 ... 4167524645 ... 2176841721 ... 6882881134 ... 4846848554 ... 5283751526 ... ''').find_first_synchronised_flash_step_index() 195 """ grid = deepcopy(self) for step_index in debugger.stepping(count(1)): debugger.report_if(step_index) grid.step() if grid.did_all_flash: return step_index raise Exception("Run out of numbers!")
def get_checksum(self, data: str, debugger: Debugger) -> str: """ >>> DataGenerator().get_checksum("110010110100") '100' >>> DataGenerator().get_checksum("110101") '100' >>> DataGenerator().get_checksum("100") '100' >>> DataGenerator().get_checksum("10000011110010000111") '01100' """ reduced = data while True: new_reduced = self.reduce_data(reduced) if new_reduced == reduced: break reduced = new_reduced if debugger.should_report(): debugger.report( f"Check summing, step: {debugger.step_count}, time: " f"{debugger.pretty_duration_since_start}, size: " f"{len(reduced)}/{len(data)}") return reduced
def get_min_step_count_to_target( self, start: str, target: str, debugger: Debugger = Debugger(enabled=False)) -> int: """ >>> MachineExtended.from_substitutions_text( ... "e => H\\n" ... "e => O\\n" ... "H => HO\\n" ... "H => OH\\n" ... "O => HH\\n" ... ).get_min_step_count_to_target("e", "HOH") 3 >>> MachineExtended.from_substitutions_text( ... "e => H\\n" ... "e => O\\n" ... "H => HO\\n" ... "H => OH\\n" ... "O => HH\\n" ... ).get_min_step_count_to_target("e", "HOHOHO") 6 """ def min_length_report_format(_debugger, message): return f"min length: {min_length}, {message}" min_length = None with debugger.adding_extra_report_format(min_length_report_format): for chain in self.get_all_possible_chains_to_target( start, target, debugger=debugger): chain_length = len(chain) - 1 if min_length is None or chain_length < min_length: min_length = chain_length return min_length
def get_all_possible_chains_to_target( self, start: str, target: str, allow_reverse: bool = True, debugger: Debugger = Debugger(enabled=False), ) -> Iterable[Tuple[str, ...]]: """ >>> list(MachineExtended().get_all_possible_chains_to_target( ... "a", "b", allow_reverse=False)) [] >>> list(MachineExtended({'H': ['O']}) ... .get_all_possible_chains_to_target( ... "HH", "OO", allow_reverse=False)) [('HH', 'OH', 'OO'), ('HH', 'HO', 'OO')] >>> list(MachineExtended({'H': ['OH']}) ... .get_all_possible_chains_to_target( ... "HH", "OHOH", allow_reverse=False)) [('HH', 'OHH', 'OHOH'), ('HH', 'HOH', 'OHOH')] >>> list(MachineExtended({'H': ['O'], 'O': ['H']}) ... .get_all_possible_chains_to_target( ... "HH", "OH", allow_reverse=False)) [('HH', 'OH'), ('HH', 'HO', 'OO', 'OH')] >>> list(MachineExtended({'OH': ['H']}) ... .get_all_possible_chains_to_target( ... "OHOH", "HH", allow_reverse=False)) [('OHOH', 'HOH', 'HH'), ('OHOH', 'OHH', 'HH')] >>> list(MachineExtended().get_all_possible_chains_to_target("a", "b")) [] >>> list(MachineExtended({'H': ['O']}) ... .get_all_possible_chains_to_target("HH", "OO")) [('HH', 'OH', 'OO'), ('HH', 'HO', 'OO')] >>> list(MachineExtended({'H': ['OH']}) ... .get_all_possible_chains_to_target("HH", "OHOH")) [('OHOH', 'HOH', 'HH'), ('OHOH', 'OHH', 'HH')] >>> list(MachineExtended({'H': ['O'], 'O': ['H']}) ... .get_all_possible_chains_to_target("HH", "OH")) [('HH', 'OH'), ('HH', 'HO', 'OO', 'OH')] """ if len(target) < len(start): if self.are_substitutions_not_decreasing(): raise Exception( f"Cannot go from length {len(start)} to length " f"{len(target)}, as substitutions don't decrease") elif len(target) > len(start): if self.are_substitutions_not_increasing(): raise Exception( f"Cannot go from length {len(start)} to length " f"{len(target)}, as substitutions don't increase") if allow_reverse: if len(target) > len(start): yield from self.reverse().get_all_possible_chains_to_target( start=target, target=start, debugger=debugger) return prune = self.get_prune(target) for chain in debugger.stepping( self.get_all_possible_chains(start, prune, debugger=debugger)): if chain[-1] == target: yield chain
def get_index_cycle( self, index, size, max_count=None, debugger: Debugger = Debugger(enabled=False), ): """ >>> list(ShufflesExtended.parse( ... "deal into new stack\\n" ... "cut -2\\n" ... "deal with increment 7\\n" ... "cut 8\\n" ... "cut -4\\n" ... "deal with increment 7\\n" ... "cut 3\\n" ... "deal with increment 9\\n" ... "deal with increment 3\\n" ... "cut -1\\n" ... ).get_index_cycle(0, 10)) [9, 6, 7, 0] >>> list(ShufflesExtended.parse( ... "deal into new stack\\n" ... "cut -2\\n" ... "deal with increment 7\\n" ... "cut 8\\n" ... "cut -4\\n" ... "deal with increment 7\\n" ... "cut 3\\n" ... "deal with increment 9\\n" ... "deal with increment 3\\n" ... "cut -1\\n" ... ).get_index_cycle(1, 10)) [2, 5, 4, 1] >>> list(ShufflesExtended.parse( ... "deal into new stack\\n" ... "cut -2\\n" ... "deal with increment 7\\n" ... "cut 8\\n" ... "cut -4\\n" ... "deal with increment 7\\n" ... "cut 3\\n" ... "deal with increment 9\\n" ... "deal with increment 3\\n" ... "cut -1\\n" ... ).get_index_cycle(3, 10)) [8, 3] """ if max_count is None: max_count = size search_index = index for step in range(max_count): search_index = self.get_index_at_position_after_shuffle( search_index, size) yield search_index if search_index == index: break debugger.default_report_if("Looking...")
def apply( self, reactor: Reactor, debugger: Debugger = Debugger(enabled=False), ) -> Reactor: for step_index, step in debugger.stepping(enumerate(self.steps, 1)): step.apply(reactor) debugger.default_report_if( f"After {step_index} steps: {reactor.cube_count} cubes are on") return reactor
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 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 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 solve(self, _input, debugger: Debugger): """ >>> Challenge().default_solve() 22887907 """ firewall = Firewall.from_ranges_text(_input) if debugger.should_report(): debugger.report(f"Total range: {firewall.total_range}") for _range in firewall.blocked_ranges: debugger.report(f" {_range}") return firewall.get_lowest_non_blocked_value()
def solve( self, debugger: Debugger = Debugger(enabled=False), ) -> "QuantumGameSearch": while not debugger.step_if(self.finished): self.advance_once() debugger.default_report_if( f"Done {self.move_count} moves, with " f"{len(self.residual_state_counts)} remaining states, " f"currently {self.player_1_wins} vs {self.player_2_wins}") return self
def get_disk_checksum( self, size: int, initial: str, debugger: Debugger, ) -> str: """ >>> DataGenerator().get_disk_checksum(20, "10000") '01100' """ debugger.report_if("Getting disk checksum") disk = self.fill_disk(size, initial, debugger=debugger.sub_debugger()) return self.get_checksum(disk, debugger=debugger.sub_debugger())
def __add__( self, other: "ModuloShuffle", debugger: Debugger = Debugger(enabled=False), ) -> "ModuloShuffle": """ >>> ShufflesExtended.parse(''' ... deal with increment 7 ... deal into new stack ... deal into new stack ... ''').to_modulo_shuffle_for_size(10).shuffle_deck(tuple(range(10))) (0, 3, 6, 9, 2, 5, 8, 1, 4, 7) >>> ShufflesExtended.parse(''' ... cut 6 ... deal with increment 7 ... deal into new stack ... ''').to_modulo_shuffle_for_size(10).shuffle_deck(tuple(range(10))) (3, 0, 7, 4, 1, 8, 5, 2, 9, 6) >>> ShufflesExtended.parse(''' ... deal with increment 7 ... deal with increment 9 ... cut -2 ... ''').to_modulo_shuffle_for_size(10).shuffle_deck(tuple(range(10))) (6, 3, 0, 7, 4, 1, 8, 5, 2, 9) >>> ShufflesExtended.parse( ... "deal into new stack\\n" ... "cut -2\\n" ... "deal with increment 7\\n" ... "cut 8\\n" ... "cut -4\\n" ... "deal with increment 7\\n" ... "cut 3\\n" ... "deal with increment 9\\n" ... "deal with increment 3\\n" ... "cut -1\\n" ... ).to_modulo_shuffle_for_size(10).shuffle_deck(tuple(range(10))) (9, 2, 5, 8, 1, 4, 7, 0, 3, 6) """ if self.size != other.size: raise Exception(f"Cannot combine shuffles with different sizes") debugger.report(f"Add {self} + {other}") cls = type(self) # noinspection PyArgumentList return cls( factor=(other.factor * self.factor) % self.size, offset=(other.offset * self.factor + self.offset) % self.size, size=self.size, )
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 find_first_valid_input( digits: Iterable[int], debugger: Debugger = Debugger(enabled=False), ) -> int: def get_next_chains( _chains: Iterable[Tuple[InputList, int]], _index: int, ) -> Iterable[Tuple[InputList, int]]: for previous_inputs, previous_state in debugger.stepping(_chains): if debugger.should_report(): debugger.default_report( f"Looking at {''.join(map(str, previous_inputs))}, giving " f"{run_program_optimised(previous_inputs, True)}/" f"{run_program_optimised_list(previous_inputs, True)}", ) for next_input in digits: state = get_program_optimised_state( previous_state, _index, next_input, ) if state >= AAC[_index]: continue yield previous_inputs + [next_input], state chains: Iterable[Tuple[InputList, int]] = (([], 0) for _ in range(1)) for index in range(14): chains = get_next_chains(chains, index) valid_inputs = (_input for _input, state in chains if not state) valid_input = next(iter(valid_inputs), None) if valid_input is None: raise Exception(f"Could not a valid input") return int("".join(map(str, valid_input)))
def from_steps_text( cls, steps_text: str, debugger: Debugger = Debugger(enabled=False), ) -> "Region2DSet": steps = RebootStepSet.from_steps_text(steps_text) return cls().apply(steps, debugger=debugger)
def fill_disk(self, size: int, initial: str, debugger: Debugger) -> str: """ >>> DataGenerator().fill_disk(20, "10000") '10000011110010000111' """ disk = initial while len(disk) < size: disk = self.increase_data(disk) if debugger.should_report(): debugger.report( f"Filling, step: {debugger.step_count}, time: " f"{debugger.pretty_duration_since_start}, size: " f"{len(disk)}/{size}") disk = disk[:size] return disk
def find_min_target_distance_for_cavern( cls, cavern: Cavern, debugger: Debugger = Debugger(enabled=False), ) -> int: measurer = cls.from_cavern(cavern) return measurer.find_min_target_distance(debugger=debugger)
def apply( self, step_set: RebootStepSet, debugger: Debugger = Debugger(enabled=False), ) -> "RegionSet": region_class = self.get_region_class() for index, step in debugger.stepping(enumerate(step_set.steps, 1)): step_region = region_class.from_step(step) if step.set_to_on: self.add(step_region) else: self.remove(step_region) debugger.default_report_if( f"Done {index}/{len(step_set.steps)}, " f"{len(self.regions)} regions, {len(self)} total cubes") return self
def apply( self, steps: RebootStepSetT, debugger: Debugger = Debugger(enabled=False), ) -> "Reactor": steps.apply(self, debugger=debugger) return self
def get_index_at_position_after_shuffle_many( self, index, count, debugger: Debugger = Debugger(enabled=False), ): return (self.__mul__(count, debugger=debugger))\ .get_index_at_position_after_shuffle(index)
def get_code( self, index: int, debugger: Debugger = Debugger(enabled=False), ) -> int: """ >>> list(map(Grid().get_code, range(1, 5))) [20151125, 31916031, 18749137, 16080970] """ max_precomputed_index = self.get_max_precomputed_index(index) code = self.pre_computed_codes[max_precomputed_index] for computing_index \ in debugger.stepping(range(index - max_precomputed_index)): debugger.default_report_if(computing_index) code *= self.multiplier code %= self.modulo return code
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 find_min_target_distance( self, debugger: Debugger = Debugger(enabled=False), ) -> int: self.measure_distances(debugger=debugger) if self.target not in self.distances: raise Exception( f"Could not find target distance, but found distances for " f"{len(self.distances)}, max point was {max(self.distances)}") return self.distances[self.target]
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 get_card_at_position_after_shuffle_many( self, deck, count, index, debugger: Debugger = Debugger(enabled=False), ): index = self.get_index_for_position_after_many_shuffles( index, count, len(deck), debugger=debugger) return deck[index]
def get_length_after_steps( self, sequence: str, step_count: int = 40, debugger: Debugger = Debugger(enabled=False), ) -> int: """ >>> LookAndSay().get_length_after_steps("1", 5) 6 """ return len(self.step_many(sequence, step_count, debugger=debugger))
def default_solve(self, _input=None, debugger=None): """Convenient method to call `solve` with the input from the disk""" if _input is None: _input = self.input if debugger is None: from aox.challenge import Debugger debugger = Debugger(enabled=False) if has_method_arguments(self.solve, "debugger"): return self.solve(_input, debugger=debugger) else: # noinspection PyArgumentList return self.solve(_input, debug=debugger)
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")