My Advent of Code solutions in Python. kevinyap.ca/2019/12/going-fast-in-advent-of-code/
advent-of-code python
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

AoC 2022!

+501
+2
.gitignore
··· 3 3 outputs/ 4 4 vm.py 5 5 search.py 6 + advent.py 7 + test.py
+32
2022/starter.py
··· 1 + import os 2 + import sys 3 + import re 4 + import math 5 + import copy 6 + import fileinput 7 + from string import ascii_uppercase, ascii_lowercase 8 + from collections import Counter, defaultdict, deque, namedtuple 9 + from itertools import count, product, permutations, combinations, combinations_with_replacement 10 + 11 + import advent 12 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping 13 + from utils import chunks, gcd, lcm, print_grid, min_max_xy 14 + from utils import new_table, transposed, rotated, firsts, lasts 15 + from utils import md5, sha256, knot_hash 16 + from utils import VOWELS, CONSONANTS 17 + from utils import Point, DIRS, DIRS_4, DIRS_8 # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 18 + 19 + # Itertools Functions: 20 + # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD 21 + # permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC 22 + # combinations('ABCD', 2) AB AC AD BC BD CD 23 + # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 24 + 25 + # day .lines() .nlines() .paragraphs() .board() .pboard() .tboard() 26 + 27 + tot = 0 28 + res = [] 29 + 30 + day = advent.Day(year=2022, day=1) 31 + 32 +
+467
2022/utils.py
··· 1 + import re 2 + import math 3 + import hashlib 4 + import operator 5 + import copy 6 + from collections import Counter 7 + from functools import total_ordering, reduce 8 + 9 + 10 + LETTERS = [x for x in 'abcdefghijklmnopqrstuvwxyz'] 11 + VOWELS = {'a', 'e', 'i', 'o', 'u'} 12 + CONSONANTS = set(x for x in LETTERS if x not in VOWELS) 13 + 14 + 15 + def parse_line(regex, line): 16 + """Returns capture groups in regex for line. Int-ifies numbers.""" 17 + ret = [] 18 + for match in re.match(regex, line).groups(): 19 + try: 20 + ret.append(int(match)) 21 + except ValueError: 22 + ret.append(match) 23 + 24 + return ret 25 + 26 + 27 + def parse_nums(line, negatives=True): 28 + """Returns a list of numbers in `line`.""" 29 + num_re = r'-?\d+' if negatives else r'\d+' 30 + return [int(n) for n in re.findall(num_re, line)] 31 + 32 + 33 + def new_table(val, width, height): 34 + return [[val for _ in range(width)] for _ in range(height)] 35 + 36 + 37 + def transposed(matrix): 38 + """Returns the transpose of the given matrix.""" 39 + return [list(r) for r in zip(*matrix)] 40 + 41 + 42 + def rotated(matrix): 43 + """Returns the given matrix rotated 90 degrees clockwise.""" 44 + return [list(r) for r in zip(*matrix[::-1])] 45 + 46 + def firsts(matrix): 47 + """Like matrix[0], but for the first column.""" 48 + return rotated(matrix)[0] 49 + 50 + def lasts(matrix): 51 + """Like matrix[-1], but for the last column.""" 52 + return rotated(matrix)[-1] 53 + 54 + 55 + def mul(lst): 56 + """Like sum(), but for multiplication.""" 57 + return reduce(operator.mul, lst, 1) # NOQA 58 + 59 + 60 + def chunks(l, n): 61 + """Yield successive n-sized chunks from l.""" 62 + for i in range(0, len(l), n): 63 + yield l[i:i + n] 64 + 65 + 66 + def all_unique(lst): 67 + return len(lst) == len(set(lst)) 68 + 69 + 70 + def factors(n): 71 + """Returns the factors of n.""" 72 + return sorted( 73 + x for tup in ( 74 + [i, n // i] for i in range(1, int(n ** 0.5) + 1) 75 + if n % i == 0) 76 + for x in tup) 77 + 78 + 79 + def gcd(a,b): 80 + """Compute the greatest common divisor of a and b""" 81 + while b > 0: 82 + a, b = b, a % b 83 + return a 84 + 85 + 86 + def lcm(a, b): 87 + """Compute the lowest common multiple of a and b""" 88 + return a * b / gcd(a, b) 89 + 90 + 91 + def egcd(a, b): 92 + x0, x1, y0, y1 = 1, 0, 0, 1 93 + while b: 94 + q, a, b = a // b, b, a % b 95 + x0, x1 = x1, x0 - q * x1 96 + y0, y1 = y1, y0 - q * y1 97 + return a, x0, y0 98 + 99 + def modinv(a, n): 100 + g, x, _ = egcd(a, n) 101 + if g == 1: 102 + return x % n 103 + else: 104 + raise ValueError("%d is not invertible mod %d" % (a, n)) 105 + 106 + def crt(rems, mods): 107 + ''' Solve a system of modular equivalences via the Chinese Remainder Theorem. 108 + Does not require pairwise coprime moduli. ''' 109 + 110 + # copy inputs 111 + orems, omods = rems, mods 112 + rems = list(rems) 113 + mods = list(mods) 114 + 115 + newrems = [] 116 + newmods = [] 117 + 118 + for i in range(len(mods)): 119 + for j in range(i+1, len(mods)): 120 + g = gcd(mods[i], mods[j]) 121 + if g == 1: 122 + continue 123 + if rems[i] % g != rems[j] % g: 124 + raise ValueError("inconsistent remainders at positions %d and %d (mod %d)" % (i, j, g)) 125 + mods[j] //= g 126 + 127 + while 1: 128 + # transfer any remaining gcds to mods[j] 129 + g = gcd(mods[i], mods[j]) 130 + if g == 1: 131 + break 132 + mods[i] //= g 133 + mods[j] *= g 134 + 135 + if mods[i] == 1: 136 + continue 137 + 138 + newrems.append(rems[i] % mods[i]) 139 + newmods.append(mods[i]) 140 + 141 + rems, mods = newrems, newmods 142 + 143 + # standard CRT 144 + s = 0 145 + n = 1 146 + for k in mods: 147 + n *= k 148 + 149 + for i in range(len(mods)): 150 + ni = n // mods[i] 151 + s += rems[i] * modinv(ni, mods[i]) * ni 152 + return s % n, n 153 + 154 + 155 + def min_max_xy(points): 156 + if len(points) == 0: 157 + return None, None, None, None 158 + if type(points[0]) == tuple: 159 + min_x = min(p[0] for p in points) 160 + max_x = max(p[0] for p in points) 161 + min_y = min(p[1] for p in points) 162 + max_y = max(p[1] for p in points) 163 + else: 164 + min_x = min(p.x for p in points) 165 + max_x = max(p.x for p in points) 166 + min_y = min(p.y for p in points) 167 + max_y = max(p.y for p in points) 168 + 169 + return min_x, max_x, min_y, max_y 170 + 171 + 172 + def print_grid(grid, f=None, quiet=False): 173 + """ 174 + Outputs `grid` to stdout. This works whether `grid` is a 2D array, 175 + or a sparse matrix (dictionary) with keys either (x, y) or Point(x, y). 176 + 177 + This function also returns a tuple (a, b), where a is the serialized 178 + representation of the grid, in case what gets printed out to stdout 179 + needs to be consumed afterwards, and b is a Counter over the values 180 + in `grid`. 181 + 182 + f: a function to transform the values of grid to something printable. 183 + quiet: don't output to stdout. 184 + """ 185 + if f is None: 186 + f = lambda x: str(x) # NOQA 187 + 188 + counts = Counter() 189 + serialized = [] 190 + 191 + if type(grid) is dict: 192 + positions = list(grid.keys()) 193 + min_x, max_x, min_y, max_y = min_max_xy(positions) 194 + if type(positions[0]) is tuple: 195 + for y in range(min_y, max_y + 1): 196 + row = ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1)) 197 + if not quiet: 198 + print(row) 199 + serialized.append(row) 200 + for c in row: 201 + counts[c] += 1 202 + 203 + else: 204 + # (x, y) => point 205 + for y in range(min_y, max_y + 1): 206 + row = ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1)) 207 + if not quiet: 208 + print(row) 209 + serialized.append(row) 210 + for c in row: 211 + counts[c] += 1 212 + else: 213 + min_x = 0 214 + min_y = 0 215 + for y in range(len(grid)): 216 + row = ''.join(f(grid[y][x]) for x in range(len(grid[0]))) 217 + if not quiet: 218 + print(row) 219 + serialized.append(row) 220 + for x, c in enumerate(row): 221 + counts[c] += 1 222 + max_x = x 223 + max_y = y 224 + 225 + if not quiet: 226 + print("height={} ({} -> {})".format(max_y - min_y + 1, min_y, max_y)) 227 + print("width={} ({} -> {})".format(max_x - min_x + 1, min_x, max_x)) 228 + print("Statistics:") 229 + for item, num in counts.most_common(): 230 + print("{}: {}".format(item, num)) 231 + 232 + return serialized, counts 233 + 234 + def resolve_mapping(candidates): 235 + """ 236 + Given a dictionary `candidates` mapping keys to candidate values, returns 237 + a dictionary where each `key` maps to a unique `value`. Hangs if intractable. 238 + 239 + Example: 240 + 241 + candidates = { 242 + 'a': [0, 1, 2], 243 + 'b': [0, 1], 244 + 'c': [0], 245 + } 246 + 247 + resolve_mapping(candidates) -> {'c': 0, 'b': 1, 'a': 2} 248 + """ 249 + resolved = {} 250 + 251 + # Ensure the mapping is key -> set(values). 252 + candidates_map = {} 253 + for k, v in candidates.items(): 254 + candidates_map[k] = set(v) 255 + 256 + while len(resolved) < len(candidates_map): 257 + for candidate in candidates_map: 258 + if len(candidates_map[candidate]) == 1 and candidate not in resolved: 259 + r = candidates_map[candidate].pop() 260 + for c in candidates_map: 261 + candidates_map[c].discard(r) 262 + 263 + resolved[candidate] = r 264 + break 265 + 266 + return resolved 267 + 268 + 269 + def memoize(f): 270 + """Simple dictionary-based memoization decorator""" 271 + cache = {} 272 + 273 + def _mem_fn(*args): 274 + hargs = (','.join(str(x) for x in args)) 275 + if hargs not in cache: 276 + cache[hargs] = f(*args) 277 + return cache[hargs] 278 + 279 + _mem_fn.cache = cache 280 + return _mem_fn 281 + 282 + 283 + def _eratosthenes(n): 284 + """http://stackoverflow.com/a/3941967/239076""" 285 + # Initialize list of primes 286 + _primes = [True] * n 287 + 288 + # Set 0 and 1 to non-prime 289 + _primes[0] = _primes[1] = False 290 + 291 + for i, is_prime in enumerate(_primes): 292 + if is_prime: 293 + yield i 294 + 295 + # Mark factors as non-prime 296 + for j in xrange(i * i, n, i): # NOQA 297 + _primes[j] = False 298 + 299 + 300 + def primes(n): 301 + """Return a list of primes from [2, n)""" 302 + return list(_eratosthenes(n)) 303 + 304 + 305 + def md5(msg): 306 + m = hashlib.md5() 307 + m.update(msg) 308 + return m.hexdigest() 309 + 310 + 311 + def sha256(msg): 312 + s = hashlib.sha256() 313 + s.update(msg) 314 + return s.hexdigest() 315 + 316 + 317 + def knot_hash(msg): 318 + lengths = [ord(x) for x in msg] + [17, 31, 73, 47, 23] 319 + sparse = range(0, 256) 320 + pos = 0 321 + skip = 0 322 + 323 + for _ in range(64): 324 + for l in lengths: 325 + for i in range(l // 2): 326 + x = (pos + i) % len(sparse) 327 + y = (pos + l - i - 1) % len(sparse) 328 + sparse[x], sparse[y] = sparse[y], sparse[x] 329 + 330 + pos = pos + l + skip % len(sparse) 331 + skip += 1 332 + 333 + hash_val = 0 334 + 335 + for i in range(16): 336 + res = 0 337 + for j in range(0, 16): 338 + res ^= sparse[(i * 16) + j] 339 + 340 + hash_val += res << ((16 - i - 1) * 8) 341 + 342 + return '%032x' % hash_val 343 + 344 + 345 + HEX_DIRS = { 346 + 'N': (1, -1, 0), 347 + 'NE': (1, 0, -1), 348 + 'SE': (0, 1, -1), 349 + 'S': (-1, 1, 0), 350 + 'SW': (-1, 0, 1), 351 + 'NW': (0, -1, 1), 352 + } 353 + 354 + 355 + def hex_distance(x, y, z): 356 + """Returns a given hex point's distance from the origin.""" 357 + return (abs(x) + abs(y) + abs(z)) // 2 358 + 359 + 360 + @total_ordering 361 + class Point: 362 + """Simple 2-dimensional point.""" 363 + def __init__(self, x, y): 364 + self.x = x 365 + self.y = y 366 + 367 + def __add__(self, other): 368 + return Point(self.x + other.x, self.y + other.y) 369 + 370 + def __sub__(self, other): 371 + return Point(self.x - other.x, self.y - other.y) 372 + 373 + def __mul__(self, n): 374 + return Point(self.x * n, self.y * n) 375 + 376 + def __div__(self, n): 377 + return Point(self.x / n, self.y / n) 378 + 379 + def __neg__(self): 380 + return Point(-self.x, -self.y) 381 + 382 + def __eq__(self, other): 383 + return self.x == other.x and self.y == other.y 384 + 385 + def __ne__(self, other): 386 + return not self == other 387 + 388 + def __lt__(self, other): 389 + return self.length < other.length 390 + 391 + def __str__(self): 392 + return "({}, {})".format(self.x, self.y) 393 + 394 + def __repr__(self): 395 + return "Point({}, {})".format(self.x, self.y) 396 + 397 + def __hash__(self): 398 + return hash(tuple((self.x, self.y))) 399 + 400 + def dist(self, other): 401 + return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2) 402 + 403 + def dist_manhattan(self, other): 404 + return abs(self.x - other.x) + abs(self.y - other.y) 405 + 406 + def angle(self, to=None): 407 + if to is None: 408 + return math.atan2(self.y, self.x) 409 + return math.atan2(self.y - to.y, self.x - to.x) 410 + 411 + def rotate(self, turns): 412 + """Returns the rotation of the Point around (0, 0) `turn` times clockwise.""" 413 + turns = turns % 4 414 + 415 + if turns == 1: 416 + return Point(self.y, -self.x) 417 + elif turns == 2: 418 + return Point(-self.x, -self.y) 419 + elif turns == 3: 420 + return Point(-self.y, self.x) 421 + else: 422 + return self 423 + 424 + @property 425 + def manhattan(self): 426 + return abs(self.x) + abs(self.y) 427 + 428 + @property 429 + def length(self): 430 + return math.sqrt(self.x ** 2 + self.y ** 2) 431 + 432 + def neighbours_4(self): 433 + return [self + p for p in DIRS_4] 434 + 435 + def neighbors_4(self): 436 + return self.neighbours_4() 437 + 438 + def neighbours(self): 439 + return self.neighbours_4() 440 + 441 + def neighbors(self): 442 + return self.neighbours() 443 + 444 + def neighbours_8(self): 445 + return [self + p for p in DIRS_8] 446 + 447 + def neighbors_8(self): 448 + return self.neighbours_8() 449 + 450 + 451 + DIRS_4 = DIRS = [ 452 + Point(0, 1), # north 453 + Point(1, 0), # east 454 + Point(0, -1), # south 455 + Point(-1, 0), # west 456 + ] 457 + 458 + DIRS_8 = [ 459 + Point(0, 1), # N 460 + Point(1, 1), # NE 461 + Point(1, 0), # E 462 + Point(1, -1), # SE 463 + Point(0, -1), # S 464 + Point(-1, -1), # SW 465 + Point(-1, 0), # W 466 + Point(-1, 1), # NW 467 + ]