···11+import fileinput
22+33+44+START_POS = 50
55+DAY_1 = 0
66+DAY_2 = 0
77+88+POS = START_POS
99+1010+for line in fileinput.input():
1111+ direction = line[0]
1212+ distance = int(line[1:])
1313+1414+ if direction == 'L':
1515+ distance *= -1
1616+1717+ new_pos = POS + distance
1818+1919+ # If new_pos >= 100, we crossed or landed on 0 going clockwise.
2020+ if new_pos >= 100:
2121+ # We crossed the number of times of the hundreds-digit.
2222+ DAY_2 += new_pos // 100
2323+2424+ # If new_pos <= 0, we crossed or landed on 0 going counterclockwise.
2525+ elif new_pos <= 0:
2626+ # We crossed the number of times of the hundreds-digit + 1.
2727+ DAY_2 += abs(new_pos // 100)
2828+2929+ # However, if we started this move at 0, remove our double-count.
3030+ if POS == 0:
3131+ DAY_2 -= 1
3232+3333+ # If we landed on 0, we need to add one more zero in this direction,
3434+ # because `-100 // 100 == 1`.
3535+ if new_pos % 100 == 0:
3636+ DAY_2 += 1
3737+3838+ # Fix our position on the dial.
3939+ POS = new_pos % 100
4040+4141+ if POS == 0:
4242+ DAY_1 += 1
4343+4444+print("Day 1:", DAY_1)
4545+print("Day 2:", DAY_2)
4646+
+640
2025/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+ except TypeError:
2424+ # None match
2525+ pass
2626+2727+ return ret
2828+2929+3030+def parse_nums(line, negatives=True):
3131+ """
3232+ Returns a list of numbers in `line`.
3333+3434+ Pass negatives=False to parse 1-2 as [1, 2].
3535+ """
3636+ num_re = r'-?\d+' if negatives else r'\d+'
3737+ return [int(n) for n in re.findall(num_re, line)]
3838+3939+4040+def new_table(width, height, val=None):
4141+ """Returns a `width` by `height` table populated with `val`."""
4242+ return [[val for _ in range(width)] for _ in range(height)]
4343+4444+4545+def transposed(matrix):
4646+ """Returns the transpose of the given matrix."""
4747+ return [list(r) for r in zip(*matrix)]
4848+4949+5050+def rotated(matrix):
5151+ """Returns the given matrix rotated 90 degrees clockwise."""
5252+ return [list(r) for r in zip(*matrix[::-1])]
5353+5454+def firsts(matrix):
5555+ """Like matrix[0], but for the first column."""
5656+ return rotated(matrix)[0]
5757+5858+def lasts(matrix):
5959+ """Like matrix[-1], but for the last column."""
6060+ return rotated(matrix)[-1]
6161+6262+6363+def mul(lst):
6464+ """Like sum(), but for multiplication."""
6565+ return reduce(operator.mul, lst, 1) # NOQA
6666+6767+6868+def chunks(l, n):
6969+ """Yield successive n-sized chunks from l."""
7070+ for i in range(0, len(l), n):
7171+ yield l[i:i + n]
7272+7373+def parts(l, n):
7474+ """Splits l into n equal parts. Excess (if it exists) returned as the n+1-th."""
7575+ m = len(l) // n
7676+ for i in range(0, n):
7777+ yield l[i*m:(i+1)*m]
7878+7979+ if len(l) % n != 0:
8080+ yield l[m*n:]
8181+8282+8383+def all_unique(lst):
8484+ """Returns True if all items in `lst` are unique."""
8585+ return len(lst) == len(set(lst))
8686+8787+8888+def topsort(graph, tiebreak=None):
8989+ """
9090+ Given a graph where graph[x] is an iterable of edges of directed
9191+ edges originating from x, returns a topologically sorted list of
9292+ nodes in the graph.
9393+9494+ If `tiebreak` is given, this lambda is passed to sorted() when
9595+ choosing what node to visit next.
9696+ """
9797+ if tiebreak is None:
9898+ tiebreak = lambda x: x
9999+100100+ visited = set()
101101+ stack = []
102102+103103+ def _topsort(node):
104104+ visited.add(node)
105105+106106+ # Reversed because the DFS causes equal level nodes to be popped backwards.
107107+ for n in sorted(graph[node], key=tiebreak, reverse=True):
108108+ if n not in visited:
109109+ _topsort(n)
110110+111111+ stack.append(node)
112112+113113+ for n in sorted(graph, key=tiebreak, reverse=True):
114114+ if not n in visited:
115115+ _topsort(n)
116116+117117+ return stack[::-1]
118118+119119+120120+def gcd(a,b):
121121+ """Compute the greatest common divisor of a and b"""
122122+ while b > 0:
123123+ a, b = b, a % b
124124+ return a
125125+126126+127127+def lcm(a, b):
128128+ """Compute the lowest common multiple of a and b"""
129129+ return a * b / gcd(a, b)
130130+131131+132132+def egcd(a, b):
133133+ x0, x1, y0, y1 = 1, 0, 0, 1
134134+ while b:
135135+ q, a, b = a // b, b, a % b
136136+ x0, x1 = x1, x0 - q * x1
137137+ y0, y1 = y1, y0 - q * y1
138138+ return a, x0, y0
139139+140140+def modinv(a, n):
141141+ g, x, _ = egcd(a, n)
142142+ if g == 1:
143143+ return x % n
144144+ else:
145145+ raise ValueError("%d is not invertible mod %d" % (a, n))
146146+147147+def crt(rems, mods):
148148+ """
149149+ Solve a system of modular equivalences via the Chinese Remainder Theorem.
150150+ Does not require pairwise coprime moduli.
151151+152152+ Returns (n, m), where n is the solution and m is the modulo.
153153+154154+ Arguments
155155+ rems: the remainders of the problem
156156+ mods: the modulos of the problem
157157+158158+ """
159159+160160+ # copy inputs
161161+ orems, omods = rems, mods
162162+ rems = list(rems)
163163+ mods = list(mods)
164164+165165+ newrems = []
166166+ newmods = []
167167+168168+ for i in range(len(mods)):
169169+ for j in range(i+1, len(mods)):
170170+ g = gcd(mods[i], mods[j])
171171+ if g == 1:
172172+ continue
173173+ if rems[i] % g != rems[j] % g:
174174+ raise ValueError("inconsistent remainders at positions %d and %d (mod %d)" % (i, j, g))
175175+ mods[j] //= g
176176+177177+ while 1:
178178+ # transfer any remaining gcds to mods[j]
179179+ g = gcd(mods[i], mods[j])
180180+ if g == 1:
181181+ break
182182+ mods[i] //= g
183183+ mods[j] *= g
184184+185185+ if mods[i] == 1:
186186+ continue
187187+188188+ newrems.append(rems[i] % mods[i])
189189+ newmods.append(mods[i])
190190+191191+ rems, mods = newrems, newmods
192192+193193+ # standard CRT
194194+ s = 0
195195+ n = 1
196196+ for k in mods:
197197+ n *= k
198198+199199+ for i in range(len(mods)):
200200+ ni = n // mods[i]
201201+ s += rems[i] * modinv(ni, mods[i]) * ni
202202+ return s % n, n
203203+204204+205205+def min_max_xy(points):
206206+ """
207207+ For a list of points, returns min_x, max_x, min_y, max_y.
208208+ This works on tuples (x, y) and Point(x, y).
209209+ """
210210+ if len(points) == 0:
211211+ return None, None, None, None
212212+ if type(points[0]) == tuple:
213213+ min_x = min(p[0] for p in points)
214214+ max_x = max(p[0] for p in points)
215215+ min_y = min(p[1] for p in points)
216216+ max_y = max(p[1] for p in points)
217217+ else:
218218+ min_x = min(p.x for p in points)
219219+ max_x = max(p.x for p in points)
220220+ min_y = min(p.y for p in points)
221221+ max_y = max(p.y for p in points)
222222+223223+ return min_x, max_x, min_y, max_y
224224+225225+226226+def print_grid(grid, f=None, quiet=False):
227227+ """
228228+ Outputs `grid` to stdout. This works whether `grid` is a 2D array,
229229+ or a sparse matrix (dictionary) with keys either (x, y) or Point(x, y).
230230+231231+ This function also returns a tuple (a, b), where a is the serialized
232232+ representation of the grid, in case what gets printed out to stdout
233233+ needs to be consumed afterwards, and b is a Counter over the values
234234+ in `grid`.
235235+236236+ Arguments:
237237+ f: a function to transform the values of grid to something printable.
238238+ quiet: don't output to stdout.
239239+240240+ Returns:
241241+ List[String]: Serialized, printable version of the grid.
242242+ Counter: The values contained in the grid.
243243+ """
244244+ if f is None:
245245+ f = lambda x: str(x) # NOQA
246246+247247+ counts = Counter()
248248+ serialized = []
249249+250250+ if type(grid) is dict:
251251+ positions = list(grid.keys())
252252+ min_x, max_x, min_y, max_y = min_max_xy(positions)
253253+ if type(positions[0]) is tuple:
254254+ for y in range(min_y, max_y + 1):
255255+ row = ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1))
256256+ if not quiet:
257257+ print(row)
258258+ serialized.append(row)
259259+ for c in row:
260260+ counts[c] += 1
261261+262262+ else:
263263+ # (x, y) => point
264264+ for y in range(min_y, max_y + 1):
265265+ row = ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1))
266266+ if not quiet:
267267+ print(row)
268268+ serialized.append(row)
269269+ for c in row:
270270+ counts[c] += 1
271271+ else:
272272+ min_x = 0
273273+ min_y = 0
274274+ for y in range(len(grid)):
275275+ row = ''.join(f(grid[y][x]) for x in range(len(grid[0])))
276276+ if not quiet:
277277+ print(row)
278278+ serialized.append(row)
279279+ for x, c in enumerate(row):
280280+ counts[c] += 1
281281+ max_x = x
282282+ max_y = y
283283+284284+ if not quiet:
285285+ print("height={} ({} -> {})".format(max_y - min_y + 1, min_y, max_y))
286286+ print("width={} ({} -> {})".format(max_x - min_x + 1, min_x, max_x))
287287+ print("Statistics:")
288288+ for item, num in counts.most_common():
289289+ print("{}: {}".format(item, num))
290290+291291+ return serialized, counts
292292+293293+def resolve_mapping(candidates):
294294+ """
295295+ Given a dictionary `candidates` mapping keys to candidate values, returns
296296+ a dictionary where each `key` maps to a unique `value`. Hangs if intractable.
297297+298298+ Example:
299299+300300+ candidates = {
301301+ 'a': [0, 1, 2],
302302+ 'b': [0, 1],
303303+ 'c': [0],
304304+ }
305305+306306+ resolve_mapping(candidates) -> {'c': 0, 'b': 1, 'a': 2}
307307+ """
308308+ resolved = {}
309309+310310+ # Ensure the mapping is key -> set(values).
311311+ candidates_map = {}
312312+ for k, v in candidates.items():
313313+ candidates_map[k] = set(v)
314314+315315+ while len(resolved) < len(candidates_map):
316316+ for candidate in candidates_map:
317317+ if len(candidates_map[candidate]) == 1 and candidate not in resolved:
318318+ r = candidates_map[candidate].pop()
319319+ for c in candidates_map:
320320+ candidates_map[c].discard(r)
321321+322322+ resolved[candidate] = r
323323+ break
324324+325325+ return resolved
326326+327327+328328+def memoize(f):
329329+ """Simple dictionary-based memoization decorator"""
330330+ cache = {}
331331+332332+ def _mem_fn(*args):
333333+ hargs = (','.join(str(x) for x in args))
334334+ if hargs not in cache:
335335+ cache[hargs] = f(*args)
336336+ return cache[hargs]
337337+338338+ _mem_fn.cache = cache
339339+ return _mem_fn
340340+341341+342342+@memoize
343343+def _eratosthenes(n):
344344+ """http://stackoverflow.com/a/3941967/239076"""
345345+ # Initialize list of primes
346346+ _primes = [True] * n
347347+348348+ # Set 0 and 1 to non-prime
349349+ _primes[0] = _primes[1] = False
350350+351351+ for i, is_prime in enumerate(_primes):
352352+ if is_prime:
353353+ yield i
354354+355355+ # Mark factors as non-prime
356356+ for j in range(i * i, n, i): # NOQA
357357+ _primes[j] = False
358358+359359+360360+@memoize
361361+def primes(n):
362362+ """Return a list of primes from [2, n)"""
363363+ return list(_eratosthenes(n))
364364+365365+366366+@memoize
367367+def factors(n):
368368+ """Returns the factors of n."""
369369+ return sorted(
370370+ x for tup in (
371371+ [i, n // i] for i in range(1, int(n ** 0.5) + 1)
372372+ if n % i == 0)
373373+ for x in tup)
374374+375375+376376+def md5(msg):
377377+ m = hashlib.md5()
378378+ m.update(msg)
379379+ return m.hexdigest()
380380+381381+382382+def sha256(msg):
383383+ s = hashlib.sha256()
384384+ s.update(msg)
385385+ return s.hexdigest()
386386+387387+def HASH(code):
388388+ val = 0
389389+ for c in code:
390390+ val += ord(c)
391391+ val *= 17
392392+ val %= 256
393393+ return val
394394+395395+def knot_hash(msg):
396396+ lengths = [ord(x) for x in msg] + [17, 31, 73, 47, 23]
397397+ sparse = range(0, 256)
398398+ pos = 0
399399+ skip = 0
400400+401401+ for _ in range(64):
402402+ for l in lengths:
403403+ for i in range(l // 2):
404404+ x = (pos + i) % len(sparse)
405405+ y = (pos + l - i - 1) % len(sparse)
406406+ sparse[x], sparse[y] = sparse[y], sparse[x]
407407+408408+ pos = pos + l + skip % len(sparse)
409409+ skip += 1
410410+411411+ hash_val = 0
412412+413413+ for i in range(16):
414414+ res = 0
415415+ for j in range(0, 16):
416416+ res ^= sparse[(i * 16) + j]
417417+418418+ hash_val += res << ((16 - i - 1) * 8)
419419+420420+ return '%032x' % hash_val
421421+422422+423423+HEX_DIRS = {
424424+ 'N': (1, -1, 0),
425425+ 'NE': (1, 0, -1),
426426+ 'SE': (0, 1, -1),
427427+ 'S': (-1, 1, 0),
428428+ 'SW': (-1, 0, 1),
429429+ 'NW': (0, -1, 1),
430430+}
431431+432432+433433+def hex_distance(x, y, z):
434434+ """Returns a given hex point's distance from the origin."""
435435+ return (abs(x) + abs(y) + abs(z)) // 2
436436+437437+438438+def polygon_perimeter(points):
439439+ """Given a set of bounding box points, returns the perimeter of the polygon."""
440440+ return sum(a.dist_manhattan(b) for a, b in zip(points, points[1:] + [points[0]]))
441441+442442+443443+def polygon_area(points):
444444+ """Given a set of integer bounding box points, returns the total area of the polygon."""
445445+ # Use shoelace formula to compute internal area.
446446+ area = 0
447447+448448+ for a, b in zip(points, points[1:] + [points[0]]):
449449+ area += (b.x + a.x) * (b.y - a.y)
450450+451451+ area = int(abs(area / 2.0))
452452+453453+ # Calculate perimeter.
454454+ perimeter = polygon_perimeter(points)
455455+456456+ # Account for outer perimeter strip in final area computation.
457457+ return area + (perimeter // 2) + 1
458458+459459+460460+@total_ordering
461461+class Point:
462462+ """Simple 2-dimensional point."""
463463+ def __init__(self, x, y):
464464+ self.x = x
465465+ self.y = y
466466+467467+ def __add__(self, other):
468468+ return Point(self.x + other.x, self.y + other.y)
469469+470470+ def __sub__(self, other):
471471+ return Point(self.x - other.x, self.y - other.y)
472472+473473+ def __mul__(self, n):
474474+ return Point(self.x * n, self.y * n)
475475+476476+ def __div__(self, n):
477477+ return Point(self.x / n, self.y / n)
478478+479479+ def __neg__(self):
480480+ return Point(-self.x, -self.y)
481481+482482+ def __eq__(self, other):
483483+ if type(other) != Point:
484484+ return False
485485+ return self.x == other.x and self.y == other.y
486486+487487+ def __ne__(self, other):
488488+ return not self == other
489489+490490+ def __lt__(self, other):
491491+ return self.length < other.length
492492+493493+ def __invert__(self):
494494+ return Point(-self.y, -self.x)
495495+496496+ def __str__(self):
497497+ return "({}, {})".format(self.x, self.y)
498498+499499+ def __repr__(self):
500500+ return "Point({}, {})".format(self.x, self.y)
501501+502502+ def __hash__(self):
503503+ return hash(tuple((self.x, self.y)))
504504+505505+ def dist(self, other):
506506+ return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
507507+508508+ def dist_manhattan(self, other):
509509+ return abs(self.x - other.x) + abs(self.y - other.y)
510510+511511+ def dist_chess(self, other):
512512+ return max(abs(self.x - other.x), abs(self.y - other.y))
513513+514514+ def dist_chebyshev(self, other):
515515+ return self.dist_chess(other)
516516+517517+ def angle(self, to=None):
518518+ if to is None:
519519+ return math.atan2(self.y, self.x)
520520+ return math.atan2(self.y - to.y, self.x - to.x)
521521+522522+ def rotate(self, turns):
523523+ """Returns the rotation of the Point around (0, 0) `turn` times clockwise."""
524524+ turns = turns % 4
525525+526526+ if turns == 1:
527527+ return Point(self.y, -self.x)
528528+ elif turns == 2:
529529+ return Point(-self.x, -self.y)
530530+ elif turns == 3:
531531+ return Point(-self.y, self.x)
532532+ else:
533533+ return self
534534+535535+ @property
536536+ def manhattan(self):
537537+ return abs(self.x) + abs(self.y)
538538+539539+ @property
540540+ def chess(self):
541541+ return max(abs(self.x), abs(self.y))
542542+543543+ @property
544544+ def chebyshev(self):
545545+ return self.chess
546546+547547+ @property
548548+ def length(self):
549549+ return math.sqrt(self.x ** 2 + self.y ** 2)
550550+551551+ def neighbours_4(self):
552552+ return [self + p for p in DIRS_4]
553553+554554+ def neighbors_4(self):
555555+ return self.neighbours_4()
556556+557557+ def neighbours(self):
558558+ return self.neighbours_4()
559559+560560+ def neighbors(self):
561561+ return self.neighbours()
562562+563563+ def neighbours_8(self):
564564+ return [self + p for p in DIRS_8]
565565+566566+ def neighbors_8(self):
567567+ return self.neighbours_8()
568568+569569+N = Point(0, 1)
570570+NE = Point(1, 1)
571571+E = Point(1, 0)
572572+SE = Point(1, -1)
573573+S = Point(0, -1)
574574+SW = Point(-1, -1)
575575+W = Point(-1, 0)
576576+NW = Point(-1, 1)
577577+578578+DIRS_4 = DIRS = [
579579+ Point(0, 1), # north
580580+ Point(1, 0), # east
581581+ Point(0, -1), # south
582582+ Point(-1, 0), # west
583583+]
584584+585585+DIRS_8 = [
586586+ Point(0, 1), # N
587587+ Point(1, 1), # NE
588588+ Point(1, 0), # E
589589+ Point(1, -1), # SE
590590+ Point(0, -1), # S
591591+ Point(-1, -1), # SW
592592+ Point(-1, 0), # W
593593+ Point(-1, 1), # NW
594594+]
595595+596596+class UnionFind:
597597+ """
598598+ If this comes in handy, thank you mcpower!
599599+ https://www.reddit.com/r/adventofcode/comments/a9c61w/2018_day_25_solutions/eci5kaf/
600600+ """
601601+ # n: int
602602+ # parents: List[Optional[int]]
603603+ # ranks: List[int]
604604+ # num_sets: int
605605+606606+ def __init__(self, n: int) -> None:
607607+ self.n = n
608608+ self.parents = [None] * n
609609+ self.ranks = [1] * n
610610+ self.num_sets = n
611611+612612+ def find(self, i: int) -> int:
613613+ p = self.parents[i]
614614+ if p is None:
615615+ return i
616616+ p = self.find(p)
617617+ self.parents[i] = p
618618+ return p
619619+620620+ def in_same_set(self, i: int, j: int) -> bool:
621621+ return self.find(i) == self.find(j)
622622+623623+ def merge(self, i: int, j: int) -> None:
624624+ i = self.find(i)
625625+ j = self.find(j)
626626+627627+ if i == j:
628628+ return
629629+630630+ i_rank = self.ranks[i]
631631+ j_rank = self.ranks[j]
632632+633633+ if i_rank < j_rank:
634634+ self.parents[i] = j
635635+ elif i_rank > j_rank:
636636+ self.parents[j] = i
637637+ else:
638638+ self.parents[j] = i
639639+ self.ranks[i] += 1
640640+ self.num_sets -= 1