Advent of Code 2022 day 16
I’ve taken to separating the two parts into different Python files now, since the code is growing in complexity. Part 2 improved upon the length of time set out by part 1 to work out the distances between the valves. However, despite this, it takes an age to run. Used permutations for part 2. I might have been able to get away with combinations, but I wanted to be safe.
with open("16_test.txt") as f: # with open("16.txt") as f: data = f.read() def get_id(line): return line.split("Valve ")[1].split(" ")[0] class Valve: def __init__(self, data): self.id = get_id(data) self.flow_rate = self.get_flow_rate(data) self.neighbours = self.get_neighbours(data) def get_flow_rate(self, line): return int(line.split("rate=")[1].split(";")[0]) def get_neighbours(self, line): if "valves " in line: return line.split("valves ")[1].split(", ") return [line.split("leads to valve ")[1]] valves = {get_id(line): Valve(line) for line in data.splitlines()} def shortest(paths): if len(paths) > 0: return sorted(paths, key=lambda x: len(x))[0] return None def get_path_to(start, end, path): if end == start: return [] elif end in valves[start].neighbours: return path+[end] elif len(path) > 1 and start in path[:-1]: return False paths = [] for x in valves[start].neighbours: if possible_path:=get_path_to(x, end, path+[x]): paths.append(possible_path) return shortest(paths) def get_distances(valves): distances = {} for start in valves: for end in valves: if start == end: distances[(start, end)] = 0 elif end in valves[start].neighbours: distances[(start, end)] = 1 else: for x in valves[start].neighbours: if end in valves[x].neighbours: distances[(start, end)] = 2 break else: distances[(start, end)] = len( get_path_to(start, end, []) ) return distances def answer(rounds, current, unopened, distances): if rounds <= 0: return 0 score = 0 for candidate in unopened: new_unopened = [x for x in unopened if x is not candidate] new_rounds = rounds-1-distances[(current.id, candidate.id)] if new_rounds <= 0: continue score = max( score, new_rounds*candidate.flow_rate+answer( new_rounds, candidate, new_unopened, distances ) ) return score distances = get_distances(valves) current = valves["AA"] unopened = {valve for valve in valves.values() if valve.flow_rate > 0} print(answer(30, current, unopened, distances)) # Part 2 from itertools import permutations # with open("16_test.txt") as f: with open("16.txt") as f: data = f.read() def get_id(line): return line.split("Valve ")[1].split(" ")[0] class Valve: def __init__(self, data): self.id = get_id(data) self.flow_rate = self.get_flow_rate(data) self.neighbours = self.get_neighbours(data) def __str__(self): return f"{self.id}" def __repr__(self): return f"{self.id}" def get_flow_rate(self, line): return int(line.split("rate=")[1].split(";")[0]) def get_neighbours(self, line): if "valves " in line: return line.split("valves ")[1].split(", ") return [line.split("leads to valve ")[1]] valves = {get_id(line): Valve(line) for line in data.splitlines()} for valve in valves.values(): valve.neighbours = [valves[x] for x in valve.neighbours] def get_distance(start, end, distances, used=None): if used is None: used = [] if distances[end][start] is not None: return distances[end][start] elif start == end: return 0 elif end in start.neighbours: return 1 min_distance = 10**6 if start in used: return min_distance used.append(start) for x in start.neighbours: min_distance = min( min_distance, get_distance(x, end, distances, used=used)+1 ) return min_distance def get_distances(valves): distances = {x: {y: None for y in valves} for x in valves} for x in valves: for y in valves: if distances[y][x] is not None: continue elif x == y: distances[y][x] = 0 elif y in x.neighbours: distances[y][x] = 1 else: distances[y][x] = get_distance(x, y, distances) return distances distances = get_distances(valves.values()) def solve(round_a, round_b, current_a, current_b, unopened, distances): if round_a <= 0: return 0 if round_b <= 0: return 0 score = 0 if len(unopened) == 0: return score elif len(unopened) == 1: new_score = 0 candidate_a = unopened[0] new_round_a = round_a - distances[candidate_a][current_a] - 1 if new_round_a > 0: new_score += candidate_a.flow_rate*new_round_a return max(score, new_score) elif len(unopened) > 1: for candidate_a, candidate_b in permutations(unopened, 2): new_score = 0 new_unopened = [ x for x in unopened if x is not candidate_a and x is not candidate_b ] new_round_a = round_a - distances[candidate_a][current_a] - 1 new_round_b = round_b - distances[candidate_b][current_b] - 1 if new_round_a > 0: new_score += candidate_a.flow_rate*new_round_a if new_round_b > 0: new_score += candidate_b.flow_rate*new_round_b score = max( score, new_score + solve( new_round_a, new_round_b, candidate_a, candidate_b, new_unopened, distances ) ) return score unopened = [x for x in valves.values() if x.flow_rate > 0] print(solve(26, 26, valves["AA"], valves["AA"], unopened, distances))