|  | 
|  | 1 | +from aoc import * | 
|  | 2 | +from itertools import pairwise, combinations, repeat, product, accumulate, permutations, cycle, combinations_with_replacement | 
|  | 3 | +from more_itertools import first_true, flatten, ncycles, first_true, zip_broadcast, windowed, chunked, take, peekable | 
|  | 4 | +import re | 
|  | 5 | +from blessed import BlessedList | 
|  | 6 | +from collections import namedtuple, defaultdict, OrderedDict, Counter, deque, UserList | 
|  | 7 | +import operator | 
|  | 8 | +from operator import itemgetter | 
|  | 9 | +from bisect import * | 
|  | 10 | +from functools import reduce, cache, cmp_to_key | 
|  | 11 | +import math | 
|  | 12 | +from copy import deepcopy | 
|  | 13 | +from math import ceil | 
|  | 14 | +from heapq import * | 
|  | 15 | +import sys | 
|  | 16 | +from pprint import pprint as pp | 
|  | 17 | +from statistics import mode | 
|  | 18 | +from string import ascii_uppercase | 
|  | 19 | +import itertools | 
|  | 20 | + | 
|  | 21 | + | 
|  | 22 | +# Priority queue implementation from | 
|  | 23 | +# https://docs.python.org/3.3/library/heapq.html#priority-queue-implementation-notes | 
|  | 24 | +class PriorityQueue(UserList): | 
|  | 25 | +    def __init__(self): | 
|  | 26 | +        self.entry_finder = {}               # mapping of tasks to entries | 
|  | 27 | +        self.REMOVED = '<removed-task>'      # placeholder for a removed task | 
|  | 28 | +        self.counter = itertools.count()     # unique sequence count | 
|  | 29 | +        UserList.__init__(self) | 
|  | 30 | + | 
|  | 31 | +    def add_task(self, task, priority=0): | 
|  | 32 | +        'Add a new task or update the priority of an existing task' | 
|  | 33 | +        if task in self.entry_finder: | 
|  | 34 | +            self.remove_task(task) | 
|  | 35 | +        count = next(self.counter) | 
|  | 36 | +        entry = [priority, count, task] | 
|  | 37 | +        self.entry_finder[task] = entry | 
|  | 38 | +        heappush(self.data, entry) | 
|  | 39 | + | 
|  | 40 | +    def remove_task(self, task): | 
|  | 41 | +        'Mark an existing task as REMOVED.  Raise KeyError if not found.' | 
|  | 42 | +        entry = self.entry_finder.pop(task) | 
|  | 43 | +        entry[-1] = self.REMOVED | 
|  | 44 | + | 
|  | 45 | +    def pop_task(self): | 
|  | 46 | +        'Remove and return the lowest priority task. Raise KeyError if empty.' | 
|  | 47 | +        while self.data: | 
|  | 48 | +            priority, count, task = heappop(self.data) | 
|  | 49 | +            if task is not self.REMOVED: | 
|  | 50 | +                del self.entry_finder[task] | 
|  | 51 | +                return task | 
|  | 52 | +        raise KeyError('pop from an empty priority queue') | 
|  | 53 | + | 
|  | 54 | +# Dijkstra from https://rosettacode.org/wiki/Dijkstra%27s_algorithm#Python | 
|  | 55 | +inf = float('inf') | 
|  | 56 | +Edge = namedtuple('Edge', ['start', 'end', 'cost']) | 
|  | 57 | + | 
|  | 58 | +class Graph(): | 
|  | 59 | +    def __init__(self, edges): | 
|  | 60 | +        self.edges = [Edge(*edge) for edge in edges] | 
|  | 61 | +        # print(dir(self.edges[0])) | 
|  | 62 | +        self.vertices = {e.start for e in self.edges} | {e.end for e in self.edges} | 
|  | 63 | + | 
|  | 64 | +    def add_edge(self, edge) | 
|  | 65 | +        self.edges.append(Edge(*edge)) | 
|  | 66 | +        self.vertices.extend({edges[0], edges[1]}) | 
|  | 67 | + | 
|  | 68 | +    def dijkstra(self, source, dest): | 
|  | 69 | +        assert source in self.vertices | 
|  | 70 | + | 
|  | 71 | +        dist = {vertex: inf for vertex in self.vertices} | 
|  | 72 | +        dist[source] = 0 | 
|  | 73 | +        previous = {vertex: None for vertex in self.vertices} | 
|  | 74 | + | 
|  | 75 | +        q = PriorityQueue() | 
|  | 76 | +        [q.add_task(v, dist[v]) for v in self.vertices] | 
|  | 77 | + | 
|  | 78 | +        neighbours = {vertex: set() for vertex in self.vertices} | 
|  | 79 | +        for start, end, cost in self.edges: | 
|  | 80 | +            neighbours[start].add((end, cost)) | 
|  | 81 | + | 
|  | 82 | +        while q: | 
|  | 83 | +            u = q.pop_task() | 
|  | 84 | +            if dist[u] == inf or u == dest: | 
|  | 85 | +                break | 
|  | 86 | +            for v, cost in neighbours[u]: | 
|  | 87 | +                alt = dist[u] + cost | 
|  | 88 | +                if alt < dist[v]:                                  # Relax (u,v,a) | 
|  | 89 | +                    dist[v] = alt | 
|  | 90 | +                    previous[v] = u | 
|  | 91 | +                    q.add_task(v, alt) | 
|  | 92 | +        s, u = deque(), dest | 
|  | 93 | +        while previous[u]: | 
|  | 94 | +            s.appendleft(u) | 
|  | 95 | +            u = previous[u] | 
|  | 96 | +        s.appendleft(u) | 
|  | 97 | +        return s, dist[dest] | 
|  | 98 | + | 
|  | 99 | + | 
|  | 100 | +DIRS = ((-1, 0), (0, 1), (1, 0), (0, -1)) | 
|  | 101 | + | 
|  | 102 | +def main(infi: str): | 
|  | 103 | +    inp = lines_stripped(infi) | 
|  | 104 | +    coords = set() | 
|  | 105 | +    # for b in range(2966, len(inp)): | 
|  | 106 | +    for b in range(len(inp)): | 
|  | 107 | +        for x in inp[:b]: | 
|  | 108 | +            nums = x.split(',') | 
|  | 109 | +            coords.add((int(nums[0]), int(nums[1]))) | 
|  | 110 | +        maxi = 71 | 
|  | 111 | +        # maxi = 7 | 
|  | 112 | +        maxj = maxi | 
|  | 113 | +        edges = [] | 
|  | 114 | +        for i, j in product(range(maxi), range(maxj)): | 
|  | 115 | +            for d in DIRS: | 
|  | 116 | +                if (i, j) not in coords and 0 <= i + d[0] < maxi and 0 <= j + d[1] < maxj and (i + d[0], j + d[1]) not in coords: | 
|  | 117 | +                    edges.append(((i, j), (i + d[0], j + d[1]), 1)) | 
|  | 118 | +                    edges.append(((i + d[0], j + d[1]), (i, j), 1)) | 
|  | 119 | +        g = Graph(edges) | 
|  | 120 | +        path, shortest_length = g.dijkstra((0, 0), (maxi-1, maxj-1)) | 
|  | 121 | +        if shortest_length == inf: | 
|  | 122 | +            return inp[b-1] | 
|  | 123 | +        # print(b, inp[b-1], shortest_length, path[-1]) | 
|  | 124 | + | 
|  | 125 | + | 
|  | 126 | +DAY = 18 | 
|  | 127 | +FILE_TEST = f"{DAY}_testa.txt" | 
|  | 128 | +# FILE_TEST = f"{DAY}_testb.txt" | 
|  | 129 | +FILE_EXP = f"{DAY}_exp.txt" | 
|  | 130 | +FILE = f"{DAY}.txt" | 
|  | 131 | +# test_and_submit(main, FILE_TEST, FILE_EXP, FILE, DAY) | 
|  | 132 | +# print(main(FILE_TEST)) | 
|  | 133 | +print(main(FILE)) | 
|  | 134 | + | 
|  | 135 | +# 140 bad | 
0 commit comments