···11+import os
22+import sys
33+import re
44+import math
55+import copy
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase
88+from collections import Counter, defaultdict, deque, namedtuple
99+from itertools import count, product, permutations, combinations, combinations_with_replacement
1010+1111+import advent
1212+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping
1313+from utils import chunks, gcd, lcm, print_grid, min_max_xy
1414+from utils import new_table, transposed, rotated, firsts, lasts
1515+from utils import md5, sha256, knot_hash
1616+from utils import VOWELS, CONSONANTS
1717+from utils import Point, DIRS, DIRS_4, DIRS_8 # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1818+1919+# Itertools Functions:
2020+# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
2121+# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC
2222+# combinations('ABCD', 2) AB AC AD BC BD CD
2323+# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
2424+2525+# day .lines() .nlines() .paragraphs() .board() .pboard() .tboard()
2626+2727+tot = 0
2828+res = []
2929+3030+day = advent.Day(year=2022, day=1)
3131+3232+
+467
2022/utils.py
···11+import re
22+import math
33+import hashlib
44+import operator
55+import copy
66+from collections import Counter
77+from functools import total_ordering, reduce
88+99+1010+LETTERS = [x for x in 'abcdefghijklmnopqrstuvwxyz']
1111+VOWELS = {'a', 'e', 'i', 'o', 'u'}
1212+CONSONANTS = set(x for x in LETTERS if x not in VOWELS)
1313+1414+1515+def parse_line(regex, line):
1616+ """Returns capture groups in regex for line. Int-ifies numbers."""
1717+ ret = []
1818+ for match in re.match(regex, line).groups():
1919+ try:
2020+ ret.append(int(match))
2121+ except ValueError:
2222+ ret.append(match)
2323+2424+ return ret
2525+2626+2727+def parse_nums(line, negatives=True):
2828+ """Returns a list of numbers in `line`."""
2929+ num_re = r'-?\d+' if negatives else r'\d+'
3030+ return [int(n) for n in re.findall(num_re, line)]
3131+3232+3333+def new_table(val, width, height):
3434+ return [[val for _ in range(width)] for _ in range(height)]
3535+3636+3737+def transposed(matrix):
3838+ """Returns the transpose of the given matrix."""
3939+ return [list(r) for r in zip(*matrix)]
4040+4141+4242+def rotated(matrix):
4343+ """Returns the given matrix rotated 90 degrees clockwise."""
4444+ return [list(r) for r in zip(*matrix[::-1])]
4545+4646+def firsts(matrix):
4747+ """Like matrix[0], but for the first column."""
4848+ return rotated(matrix)[0]
4949+5050+def lasts(matrix):
5151+ """Like matrix[-1], but for the last column."""
5252+ return rotated(matrix)[-1]
5353+5454+5555+def mul(lst):
5656+ """Like sum(), but for multiplication."""
5757+ return reduce(operator.mul, lst, 1) # NOQA
5858+5959+6060+def chunks(l, n):
6161+ """Yield successive n-sized chunks from l."""
6262+ for i in range(0, len(l), n):
6363+ yield l[i:i + n]
6464+6565+6666+def all_unique(lst):
6767+ return len(lst) == len(set(lst))
6868+6969+7070+def factors(n):
7171+ """Returns the factors of n."""
7272+ return sorted(
7373+ x for tup in (
7474+ [i, n // i] for i in range(1, int(n ** 0.5) + 1)
7575+ if n % i == 0)
7676+ for x in tup)
7777+7878+7979+def gcd(a,b):
8080+ """Compute the greatest common divisor of a and b"""
8181+ while b > 0:
8282+ a, b = b, a % b
8383+ return a
8484+8585+8686+def lcm(a, b):
8787+ """Compute the lowest common multiple of a and b"""
8888+ return a * b / gcd(a, b)
8989+9090+9191+def egcd(a, b):
9292+ x0, x1, y0, y1 = 1, 0, 0, 1
9393+ while b:
9494+ q, a, b = a // b, b, a % b
9595+ x0, x1 = x1, x0 - q * x1
9696+ y0, y1 = y1, y0 - q * y1
9797+ return a, x0, y0
9898+9999+def modinv(a, n):
100100+ g, x, _ = egcd(a, n)
101101+ if g == 1:
102102+ return x % n
103103+ else:
104104+ raise ValueError("%d is not invertible mod %d" % (a, n))
105105+106106+def crt(rems, mods):
107107+ ''' Solve a system of modular equivalences via the Chinese Remainder Theorem.
108108+ Does not require pairwise coprime moduli. '''
109109+110110+ # copy inputs
111111+ orems, omods = rems, mods
112112+ rems = list(rems)
113113+ mods = list(mods)
114114+115115+ newrems = []
116116+ newmods = []
117117+118118+ for i in range(len(mods)):
119119+ for j in range(i+1, len(mods)):
120120+ g = gcd(mods[i], mods[j])
121121+ if g == 1:
122122+ continue
123123+ if rems[i] % g != rems[j] % g:
124124+ raise ValueError("inconsistent remainders at positions %d and %d (mod %d)" % (i, j, g))
125125+ mods[j] //= g
126126+127127+ while 1:
128128+ # transfer any remaining gcds to mods[j]
129129+ g = gcd(mods[i], mods[j])
130130+ if g == 1:
131131+ break
132132+ mods[i] //= g
133133+ mods[j] *= g
134134+135135+ if mods[i] == 1:
136136+ continue
137137+138138+ newrems.append(rems[i] % mods[i])
139139+ newmods.append(mods[i])
140140+141141+ rems, mods = newrems, newmods
142142+143143+ # standard CRT
144144+ s = 0
145145+ n = 1
146146+ for k in mods:
147147+ n *= k
148148+149149+ for i in range(len(mods)):
150150+ ni = n // mods[i]
151151+ s += rems[i] * modinv(ni, mods[i]) * ni
152152+ return s % n, n
153153+154154+155155+def min_max_xy(points):
156156+ if len(points) == 0:
157157+ return None, None, None, None
158158+ if type(points[0]) == tuple:
159159+ min_x = min(p[0] for p in points)
160160+ max_x = max(p[0] for p in points)
161161+ min_y = min(p[1] for p in points)
162162+ max_y = max(p[1] for p in points)
163163+ else:
164164+ min_x = min(p.x for p in points)
165165+ max_x = max(p.x for p in points)
166166+ min_y = min(p.y for p in points)
167167+ max_y = max(p.y for p in points)
168168+169169+ return min_x, max_x, min_y, max_y
170170+171171+172172+def print_grid(grid, f=None, quiet=False):
173173+ """
174174+ Outputs `grid` to stdout. This works whether `grid` is a 2D array,
175175+ or a sparse matrix (dictionary) with keys either (x, y) or Point(x, y).
176176+177177+ This function also returns a tuple (a, b), where a is the serialized
178178+ representation of the grid, in case what gets printed out to stdout
179179+ needs to be consumed afterwards, and b is a Counter over the values
180180+ in `grid`.
181181+182182+ f: a function to transform the values of grid to something printable.
183183+ quiet: don't output to stdout.
184184+ """
185185+ if f is None:
186186+ f = lambda x: str(x) # NOQA
187187+188188+ counts = Counter()
189189+ serialized = []
190190+191191+ if type(grid) is dict:
192192+ positions = list(grid.keys())
193193+ min_x, max_x, min_y, max_y = min_max_xy(positions)
194194+ if type(positions[0]) is tuple:
195195+ for y in range(min_y, max_y + 1):
196196+ row = ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1))
197197+ if not quiet:
198198+ print(row)
199199+ serialized.append(row)
200200+ for c in row:
201201+ counts[c] += 1
202202+203203+ else:
204204+ # (x, y) => point
205205+ for y in range(min_y, max_y + 1):
206206+ row = ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1))
207207+ if not quiet:
208208+ print(row)
209209+ serialized.append(row)
210210+ for c in row:
211211+ counts[c] += 1
212212+ else:
213213+ min_x = 0
214214+ min_y = 0
215215+ for y in range(len(grid)):
216216+ row = ''.join(f(grid[y][x]) for x in range(len(grid[0])))
217217+ if not quiet:
218218+ print(row)
219219+ serialized.append(row)
220220+ for x, c in enumerate(row):
221221+ counts[c] += 1
222222+ max_x = x
223223+ max_y = y
224224+225225+ if not quiet:
226226+ print("height={} ({} -> {})".format(max_y - min_y + 1, min_y, max_y))
227227+ print("width={} ({} -> {})".format(max_x - min_x + 1, min_x, max_x))
228228+ print("Statistics:")
229229+ for item, num in counts.most_common():
230230+ print("{}: {}".format(item, num))
231231+232232+ return serialized, counts
233233+234234+def resolve_mapping(candidates):
235235+ """
236236+ Given a dictionary `candidates` mapping keys to candidate values, returns
237237+ a dictionary where each `key` maps to a unique `value`. Hangs if intractable.
238238+239239+ Example:
240240+241241+ candidates = {
242242+ 'a': [0, 1, 2],
243243+ 'b': [0, 1],
244244+ 'c': [0],
245245+ }
246246+247247+ resolve_mapping(candidates) -> {'c': 0, 'b': 1, 'a': 2}
248248+ """
249249+ resolved = {}
250250+251251+ # Ensure the mapping is key -> set(values).
252252+ candidates_map = {}
253253+ for k, v in candidates.items():
254254+ candidates_map[k] = set(v)
255255+256256+ while len(resolved) < len(candidates_map):
257257+ for candidate in candidates_map:
258258+ if len(candidates_map[candidate]) == 1 and candidate not in resolved:
259259+ r = candidates_map[candidate].pop()
260260+ for c in candidates_map:
261261+ candidates_map[c].discard(r)
262262+263263+ resolved[candidate] = r
264264+ break
265265+266266+ return resolved
267267+268268+269269+def memoize(f):
270270+ """Simple dictionary-based memoization decorator"""
271271+ cache = {}
272272+273273+ def _mem_fn(*args):
274274+ hargs = (','.join(str(x) for x in args))
275275+ if hargs not in cache:
276276+ cache[hargs] = f(*args)
277277+ return cache[hargs]
278278+279279+ _mem_fn.cache = cache
280280+ return _mem_fn
281281+282282+283283+def _eratosthenes(n):
284284+ """http://stackoverflow.com/a/3941967/239076"""
285285+ # Initialize list of primes
286286+ _primes = [True] * n
287287+288288+ # Set 0 and 1 to non-prime
289289+ _primes[0] = _primes[1] = False
290290+291291+ for i, is_prime in enumerate(_primes):
292292+ if is_prime:
293293+ yield i
294294+295295+ # Mark factors as non-prime
296296+ for j in xrange(i * i, n, i): # NOQA
297297+ _primes[j] = False
298298+299299+300300+def primes(n):
301301+ """Return a list of primes from [2, n)"""
302302+ return list(_eratosthenes(n))
303303+304304+305305+def md5(msg):
306306+ m = hashlib.md5()
307307+ m.update(msg)
308308+ return m.hexdigest()
309309+310310+311311+def sha256(msg):
312312+ s = hashlib.sha256()
313313+ s.update(msg)
314314+ return s.hexdigest()
315315+316316+317317+def knot_hash(msg):
318318+ lengths = [ord(x) for x in msg] + [17, 31, 73, 47, 23]
319319+ sparse = range(0, 256)
320320+ pos = 0
321321+ skip = 0
322322+323323+ for _ in range(64):
324324+ for l in lengths:
325325+ for i in range(l // 2):
326326+ x = (pos + i) % len(sparse)
327327+ y = (pos + l - i - 1) % len(sparse)
328328+ sparse[x], sparse[y] = sparse[y], sparse[x]
329329+330330+ pos = pos + l + skip % len(sparse)
331331+ skip += 1
332332+333333+ hash_val = 0
334334+335335+ for i in range(16):
336336+ res = 0
337337+ for j in range(0, 16):
338338+ res ^= sparse[(i * 16) + j]
339339+340340+ hash_val += res << ((16 - i - 1) * 8)
341341+342342+ return '%032x' % hash_val
343343+344344+345345+HEX_DIRS = {
346346+ 'N': (1, -1, 0),
347347+ 'NE': (1, 0, -1),
348348+ 'SE': (0, 1, -1),
349349+ 'S': (-1, 1, 0),
350350+ 'SW': (-1, 0, 1),
351351+ 'NW': (0, -1, 1),
352352+}
353353+354354+355355+def hex_distance(x, y, z):
356356+ """Returns a given hex point's distance from the origin."""
357357+ return (abs(x) + abs(y) + abs(z)) // 2
358358+359359+360360+@total_ordering
361361+class Point:
362362+ """Simple 2-dimensional point."""
363363+ def __init__(self, x, y):
364364+ self.x = x
365365+ self.y = y
366366+367367+ def __add__(self, other):
368368+ return Point(self.x + other.x, self.y + other.y)
369369+370370+ def __sub__(self, other):
371371+ return Point(self.x - other.x, self.y - other.y)
372372+373373+ def __mul__(self, n):
374374+ return Point(self.x * n, self.y * n)
375375+376376+ def __div__(self, n):
377377+ return Point(self.x / n, self.y / n)
378378+379379+ def __neg__(self):
380380+ return Point(-self.x, -self.y)
381381+382382+ def __eq__(self, other):
383383+ return self.x == other.x and self.y == other.y
384384+385385+ def __ne__(self, other):
386386+ return not self == other
387387+388388+ def __lt__(self, other):
389389+ return self.length < other.length
390390+391391+ def __str__(self):
392392+ return "({}, {})".format(self.x, self.y)
393393+394394+ def __repr__(self):
395395+ return "Point({}, {})".format(self.x, self.y)
396396+397397+ def __hash__(self):
398398+ return hash(tuple((self.x, self.y)))
399399+400400+ def dist(self, other):
401401+ return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
402402+403403+ def dist_manhattan(self, other):
404404+ return abs(self.x - other.x) + abs(self.y - other.y)
405405+406406+ def angle(self, to=None):
407407+ if to is None:
408408+ return math.atan2(self.y, self.x)
409409+ return math.atan2(self.y - to.y, self.x - to.x)
410410+411411+ def rotate(self, turns):
412412+ """Returns the rotation of the Point around (0, 0) `turn` times clockwise."""
413413+ turns = turns % 4
414414+415415+ if turns == 1:
416416+ return Point(self.y, -self.x)
417417+ elif turns == 2:
418418+ return Point(-self.x, -self.y)
419419+ elif turns == 3:
420420+ return Point(-self.y, self.x)
421421+ else:
422422+ return self
423423+424424+ @property
425425+ def manhattan(self):
426426+ return abs(self.x) + abs(self.y)
427427+428428+ @property
429429+ def length(self):
430430+ return math.sqrt(self.x ** 2 + self.y ** 2)
431431+432432+ def neighbours_4(self):
433433+ return [self + p for p in DIRS_4]
434434+435435+ def neighbors_4(self):
436436+ return self.neighbours_4()
437437+438438+ def neighbours(self):
439439+ return self.neighbours_4()
440440+441441+ def neighbors(self):
442442+ return self.neighbours()
443443+444444+ def neighbours_8(self):
445445+ return [self + p for p in DIRS_8]
446446+447447+ def neighbors_8(self):
448448+ return self.neighbours_8()
449449+450450+451451+DIRS_4 = DIRS = [
452452+ Point(0, 1), # north
453453+ Point(1, 0), # east
454454+ Point(0, -1), # south
455455+ Point(-1, 0), # west
456456+]
457457+458458+DIRS_8 = [
459459+ Point(0, 1), # N
460460+ Point(1, 1), # NE
461461+ Point(1, 0), # E
462462+ Point(1, -1), # SE
463463+ Point(0, -1), # S
464464+ Point(-1, -1), # SW
465465+ Point(-1, 0), # W
466466+ Point(-1, 1), # NW
467467+]