···11+import fileinput
22+33+44+readings = [int(line) for line in fileinput.input()]
55+66+part_1 = 0
77+part_2 = 0
88+99+1010+for i in range(1, len(readings)):
1111+ if readings[i] > readings[i-1]:
1212+ part_1 += 1
1313+1414+for i in range(3, len(readings)):
1515+ if readings[i] > readings[i-3]:
1616+ part_2 += 1
1717+1818+1919+print "Part 1:", part_1
2020+print "Part 2:", part_2
+23
2021/day02.py
···11+import fileinput
22+33+pos = 0
44+aim = 0
55+part_1_depth = 0
66+part_2_depth = 0
77+88+for line in fileinput.input():
99+ ins, num = line.split()
1010+ num = int(num)
1111+1212+ if ins == 'forward':
1313+ pos += num
1414+ part_2_depth += (aim * num)
1515+ elif ins == 'down':
1616+ part_1_depth += num
1717+ aim += num
1818+ elif ins == "up":
1919+ part_1_depth -= num
2020+ aim -= num
2121+2222+print "Part 1:", pos * part_1_depth
2323+print "Part 2:", pos * part_2_depth
+46
2021/day03.py
···11+import fileinput
22+from collections import Counter
33+44+55+report = [line.strip() for line in fileinput.input()]
66+77+88+# Part 1
99+gamma = ''
1010+epsilon = ''
1111+for i in range(len(report[0])):
1212+ c = Counter(r[i] for r in report)
1313+ gamma += c.most_common()[0][0]
1414+ epsilon += c.most_common()[1][0]
1515+1616+gamma = int(gamma, 2)
1717+epsilon = int(epsilon, 2)
1818+1919+print "Part 1:", gamma * epsilon
2020+2121+2222+# Part 2
2323+def part_2(report, idx, oxygen=True):
2424+ # Figure out what the most/least common bit is.
2525+ c = Counter(r[idx] for r in report)
2626+ keep = '1' if oxygen else '0'
2727+ comm = c.most_common()
2828+2929+ if comm[0][1] != comm[1][1]:
3030+ if oxygen:
3131+ keep = comm[0][0]
3232+ else:
3333+ keep = comm[1][0]
3434+3535+ valid = [r for r in report if r[idx] == keep]
3636+3737+ if len(valid) == 1:
3838+ return valid[0]
3939+4040+ return part_2(valid, idx + 1, oxygen=oxygen)
4141+4242+4343+o2 = int(part_2(report, 0, oxygen=True), 2)
4444+co2 = int(part_2(report, 0, oxygen=False), 2)
4545+4646+print "Part 2:", o2 * co2
+56
2021/day04.py
···11+import fileinput
22+from collections import OrderedDict
33+44+55+# Read input
66+data = ''.join([line for line in fileinput.input()])
77+groups = [g.split('\n') for g in data.split('\n\n')]
88+99+order = groups[0]
1010+order = [int(x) for x in order[0].split(',')]
1111+1212+boards = groups[1:]
1313+1414+1515+BOARDS = []
1616+for b in boards:
1717+ board = [[int(x) for x in line.split()] for line in b]
1818+ BOARDS.append(board)
1919+2020+MARKS = [[[False for _ in range(5)] for _ in range(5)] for _ in range(len(BOARDS))]
2121+2222+2323+def is_win(marks):
2424+ for line in marks:
2525+ if all(line):
2626+ return True
2727+2828+ for line in zip(*marks):
2929+ if all(line):
3030+ return True
3131+3232+ return False
3333+3434+3535+scores = OrderedDict()
3636+3737+for n in order:
3838+ for board, marks in zip(BOARDS, MARKS):
3939+ for y, line in enumerate(board):
4040+ for x, val in enumerate(line):
4141+ if n == val:
4242+ marks[y][x] = True
4343+4444+ # Check for winners
4545+ for i, b in enumerate(MARKS):
4646+ if is_win(b) and i not in scores:
4747+ unmarked = 0
4848+ for y, line in enumerate(MARKS[i]):
4949+ for x, val in enumerate(line):
5050+ if not val:
5151+ unmarked += BOARDS[i][y][x]
5252+5353+ scores[i] = unmarked * n
5454+5555+print "Part 1:", scores.values()[0]
5656+print "Part 2:", scores.values()[-1]
+45
2021/day05.py
···11+import fileinput
22+from collections import Counter
33+from utils import parse_nums
44+55+lines = []
66+77+for line in fileinput.input():
88+ nums = parse_nums(line)
99+ lines.append(nums)
1010+1111+part_1 = Counter()
1212+part_2 = Counter()
1313+1414+for (x1, y1, x2, y2) in lines:
1515+ # Horizontal or vertical
1616+ if x1 == x2 or y1 == y2:
1717+ if x1 > x2:
1818+ x1, x2 = x2, x1
1919+2020+ if y1 > y2:
2121+ y1, y2 = y2, y1
2222+2323+ for y in range(y1, y2 + 1):
2424+ for x in range(x1, x2 + 1):
2525+ part_1[x, y] += 1
2626+ part_2[x, y] += 1
2727+2828+ # Diagonal
2929+ else:
3030+ delta = abs(x2 - x1)
3131+ x, y = x1, y1
3232+ for n in range(delta + 1):
3333+ part_2[x, y] += 1
3434+ if x2 > x1:
3535+ x += 1
3636+ else:
3737+ x -= 1
3838+3939+ if y2 > y1:
4040+ y += 1
4141+ else:
4242+ y -= 1
4343+4444+print "Part 1:", sum(1 for v in part_1.values() if v > 1)
4545+print "Part 2:", sum(1 for v in part_2.values() if v > 1)
+26
2021/day06.py
···11+import fileinput
22+from collections import Counter
33+44+lanternfish = [int(x) for x in fileinput.input()[0].split(',')]
55+66+state = Counter()
77+88+for n in lanternfish:
99+ state[n] += 1
1010+1111+for i in range(1, 256 + 1):
1212+ next_state = Counter()
1313+ for age, count in state.items():
1414+ if age == 0:
1515+ next_state[6] += count
1616+ next_state[8] += count
1717+ else:
1818+ next_state[age - 1] += count
1919+2020+ state = next_state
2121+2222+ if i == 80:
2323+ print "Part 1:", sum(state.values())
2424+2525+print "Part 2:", sum(state.values())
2626+
+22
2021/day07.py
···11+import fileinput
22+33+44+CRABS = [int(x) for x in fileinput.input()[0].split(',')]
55+66+part_1 = part_2 = 10000000000000
77+88+for pos in range(max(CRABS) + 1):
99+ part_1_fuel = 0
1010+ part_2_fuel = 0
1111+1212+ for c in CRABS:
1313+ part_1_fuel += abs(pos - c)
1414+1515+ delta = abs(pos - c)
1616+ part_2_fuel += ((delta + 1) * delta) // 2
1717+1818+ part_1 = min(part_1, part_1_fuel)
1919+ part_2 = min(part_2, part_2_fuel)
2020+2121+print "Part 1:", part_1
2222+print "Part 2:", part_2
+43
2021/starter.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import copy # NOQA
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase # NOQA
88+from collections import Counter, defaultdict, deque, namedtuple # NOQA
99+from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
1010+1111+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping # NOQA
1212+from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA
1313+from utils import new_table, transposed, rotated # NOQA
1414+from utils import md5, sha256, knot_hash # NOQA
1515+from utils import VOWELS, CONSONANTS # NOQA
1616+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1717+1818+# Itertools Functions:
1919+# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
2020+# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC
2121+# combinations('ABCD', 2) AB AC AD BC BD CD
2222+# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
2323+2424+tot = 0
2525+res = []
2626+board = {}
2727+table = new_table(None, width=2, height=4)
2828+2929+# Uncomment for multi-group style inputs. :c
3030+# data = ''.join([line for line in fileinput.input()])
3131+# groups = [g.split('\n') for g in data.split('\n\n')]
3232+3333+for y, line in enumerate(fileinput.input()):
3434+ line = line.strip()
3535+ nums = parse_nums(line)
3636+ data = parse_line(r'', line)
3737+3838+ for x, c in enumerate(line):
3939+ board[Point(x, y)] = c
4040+4141+ if y == 0:
4242+ print(data)
4343+
+441
2021/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
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+ ret = []
1717+ for match in re.match(regex, line).groups():
1818+ try:
1919+ ret.append(int(match))
2020+ except ValueError:
2121+ ret.append(match)
2222+2323+ return ret
2424+2525+2626+def parse_nums(line, negatives=True):
2727+ num_re = r'-?\d+' if negatives else r'\d+'
2828+ return [int(n) for n in re.findall(num_re, line)]
2929+3030+3131+def new_table(val, width, height):
3232+ return [[val for _ in range(width)] for _ in range(height)]
3333+3434+3535+def transposed(matrix):
3636+ """Returns the transpose of the given matrix."""
3737+ return [list(r) for r in zip(*matrix)]
3838+3939+4040+def rotated(matrix):
4141+ """Returns the given matrix rotated 90 degrees clockwise."""
4242+ return [list(r) for r in zip(*matrix[::-1])]
4343+4444+4545+def mul(lst):
4646+ """Like sum(), but for multiplication."""
4747+ return reduce(operator.mul, lst, 1) # NOQA
4848+4949+5050+def chunks(l, n):
5151+ """Yield successive n-sized chunks from l."""
5252+ for i in range(0, len(l), n):
5353+ yield l[i:i + n]
5454+5555+5656+def all_unique(lst):
5757+ return len(lst) == len(set(lst))
5858+5959+6060+def factors(n):
6161+ """Returns the factors of n."""
6262+ return sorted(
6363+ x for tup in (
6464+ [i, n // i] for i in range(1, int(n ** 0.5) + 1)
6565+ if n % i == 0)
6666+ for x in tup)
6767+6868+6969+def gcd(a,b):
7070+ """Compute the greatest common divisor of a and b"""
7171+ while b > 0:
7272+ a, b = b, a % b
7373+ return a
7474+7575+7676+def lcm(a, b):
7777+ """Compute the lowest common multiple of a and b"""
7878+ return a * b / gcd(a, b)
7979+8080+8181+def egcd(a, b):
8282+ x0, x1, y0, y1 = 1, 0, 0, 1
8383+ while b:
8484+ q, a, b = a // b, b, a % b
8585+ x0, x1 = x1, x0 - q * x1
8686+ y0, y1 = y1, y0 - q * y1
8787+ return a, x0, y0
8888+8989+def modinv(a, n):
9090+ g, x, _ = egcd(a, n)
9191+ if g == 1:
9292+ return x % n
9393+ else:
9494+ raise ValueError("%d is not invertible mod %d" % (a, n))
9595+9696+def crt(rems, mods):
9797+ ''' Solve a system of modular equivalences via the Chinese Remainder Theorem.
9898+ Does not require pairwise coprime moduli. '''
9999+100100+ # copy inputs
101101+ orems, omods = rems, mods
102102+ rems = list(rems)
103103+ mods = list(mods)
104104+105105+ newrems = []
106106+ newmods = []
107107+108108+ for i in range(len(mods)):
109109+ for j in range(i+1, len(mods)):
110110+ g = gcd(mods[i], mods[j])
111111+ if g == 1:
112112+ continue
113113+ if rems[i] % g != rems[j] % g:
114114+ raise ValueError("inconsistent remainders at positions %d and %d (mod %d)" % (i, j, g))
115115+ mods[j] //= g
116116+117117+ while 1:
118118+ # transfer any remaining gcds to mods[j]
119119+ g = gcd(mods[i], mods[j])
120120+ if g == 1:
121121+ break
122122+ mods[i] //= g
123123+ mods[j] *= g
124124+125125+ if mods[i] == 1:
126126+ continue
127127+128128+ newrems.append(rems[i] % mods[i])
129129+ newmods.append(mods[i])
130130+131131+ rems, mods = newrems, newmods
132132+133133+ # standard CRT
134134+ s = 0
135135+ n = 1
136136+ for k in mods:
137137+ n *= k
138138+139139+ for i in range(len(mods)):
140140+ ni = n // mods[i]
141141+ s += rems[i] * modinv(ni, mods[i]) * ni
142142+ return s % n, n
143143+144144+145145+def min_max_xy(points):
146146+ if len(points) == 0:
147147+ return None, None, None, None
148148+ if type(points[0]) == tuple:
149149+ min_x = min(p[0] for p in points)
150150+ max_x = max(p[0] for p in points)
151151+ min_y = min(p[1] for p in points)
152152+ max_y = max(p[1] for p in points)
153153+ else:
154154+ min_x = min(p.x for p in points)
155155+ max_x = max(p.x for p in points)
156156+ min_y = min(p.y for p in points)
157157+ max_y = max(p.y for p in points)
158158+159159+ return min_x, max_x, min_y, max_y
160160+161161+162162+def print_grid(grid, f=None, quiet=False):
163163+ if f is None:
164164+ f = lambda x: x # NOQA
165165+166166+ counts = Counter()
167167+ serialized = []
168168+169169+ if type(grid) is dict:
170170+ positions = grid.keys()
171171+ min_x, max_x, min_y, max_y = min_max_xy(positions)
172172+ if type(positions[0]) is tuple:
173173+ for y in range(min_y, max_y + 1):
174174+ row = ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1))
175175+ if not quiet:
176176+ print row
177177+ serialized.append(row)
178178+ for c in row:
179179+ counts[c] += 1
180180+181181+ else:
182182+ # (x, y) => point
183183+ for y in range(min_y, max_y + 1):
184184+ row = ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1))
185185+ if not quiet:
186186+ print row
187187+ serialized.append(row)
188188+ for c in row:
189189+ counts[c] += 1
190190+ else:
191191+ for y in range(len(grid)):
192192+ row = ''.join(f(grid[y][x]) for x in range(len(grid[0])))
193193+ if not quiet:
194194+ print row
195195+ serialized.append(row)
196196+ for c in row:
197197+ counts[c] += 1
198198+199199+ if not quiet:
200200+ print "height={} ({} -> {})".format(max_y - min_y + 1, min_y, max_y)
201201+ print "width={} ({} -> {})".format(max_x - min_x + 1, min_x, max_x)
202202+ print "Statistics:"
203203+ for item, num in counts.most_common():
204204+ print "{}: {}".format(item, num)
205205+206206+ return serialized
207207+208208+def resolve_mapping(candidates):
209209+ """
210210+ Given a dictionary `candidates` mapping keys to candidate values, returns
211211+ a dictionary where each `key` maps to a unique `value`. Hangs if intractable.
212212+213213+ Example:
214214+215215+ candidates = {
216216+ 'a': [0, 1, 2],
217217+ 'b': [0, 1],
218218+ 'c': [0],
219219+ }
220220+221221+ resolve_mapping(candidates) -> {'c': 0, 'b': 1, 'a': 2}
222222+ """
223223+ resolved = {}
224224+225225+ # Ensure the mapping is key -> set(values).
226226+ candidates_map = {}
227227+ for k, v in candidates.items():
228228+ candidates_map[k] = set(v)
229229+230230+ while len(resolved) < len(candidates_map):
231231+ for candidate in candidates_map:
232232+ if len(candidates_map[candidate]) == 1 and candidate not in resolved:
233233+ r = candidates_map[candidate].pop()
234234+ for c in candidates_map:
235235+ candidates_map[c].discard(r)
236236+237237+ resolved[candidate] = r
238238+ break
239239+240240+ return resolved
241241+242242+243243+def memoize(f):
244244+ """Simple dictionary-based memoization decorator"""
245245+ cache = {}
246246+247247+ def _mem_fn(*args):
248248+ hargs = (','.join(str(x) for x in args))
249249+ if hargs not in cache:
250250+ cache[hargs] = f(*args)
251251+ return cache[hargs]
252252+253253+ _mem_fn.cache = cache
254254+ return _mem_fn
255255+256256+257257+def _eratosthenes(n):
258258+ """http://stackoverflow.com/a/3941967/239076"""
259259+ # Initialize list of primes
260260+ _primes = [True] * n
261261+262262+ # Set 0 and 1 to non-prime
263263+ _primes[0] = _primes[1] = False
264264+265265+ for i, is_prime in enumerate(_primes):
266266+ if is_prime:
267267+ yield i
268268+269269+ # Mark factors as non-prime
270270+ for j in xrange(i * i, n, i): # NOQA
271271+ _primes[j] = False
272272+273273+274274+def primes(n):
275275+ """Return a list of primes from [2, n)"""
276276+ return list(_eratosthenes(n))
277277+278278+279279+def md5(msg):
280280+ m = hashlib.md5()
281281+ m.update(msg)
282282+ return m.hexdigest()
283283+284284+285285+def sha256(msg):
286286+ s = hashlib.sha256()
287287+ s.update(msg)
288288+ return s.hexdigest()
289289+290290+291291+def knot_hash(msg):
292292+ lengths = [ord(x) for x in msg] + [17, 31, 73, 47, 23]
293293+ sparse = range(0, 256)
294294+ pos = 0
295295+ skip = 0
296296+297297+ for _ in range(64):
298298+ for l in lengths:
299299+ for i in range(l // 2):
300300+ x = (pos + i) % len(sparse)
301301+ y = (pos + l - i - 1) % len(sparse)
302302+ sparse[x], sparse[y] = sparse[y], sparse[x]
303303+304304+ pos = pos + l + skip % len(sparse)
305305+ skip += 1
306306+307307+ hash_val = 0
308308+309309+ for i in range(16):
310310+ res = 0
311311+ for j in range(0, 16):
312312+ res ^= sparse[(i * 16) + j]
313313+314314+ hash_val += res << ((16 - i - 1) * 8)
315315+316316+ return '%032x' % hash_val
317317+318318+319319+HEX_DIRS = {
320320+ 'N': (1, -1, 0),
321321+ 'NE': (1, 0, -1),
322322+ 'SE': (0, 1, -1),
323323+ 'S': (-1, 1, 0),
324324+ 'SW': (-1, 0, 1),
325325+ 'NW': (0, -1, 1),
326326+}
327327+328328+329329+def hex_distance(x, y, z):
330330+ """Returns a given hex point's distance from the origin."""
331331+ return (abs(x) + abs(y) + abs(z)) // 2
332332+333333+334334+@total_ordering
335335+class Point:
336336+ """Simple 2-dimensional point."""
337337+ def __init__(self, x, y):
338338+ self.x = x
339339+ self.y = y
340340+341341+ def __add__(self, other):
342342+ return Point(self.x + other.x, self.y + other.y)
343343+344344+ def __sub__(self, other):
345345+ return Point(self.x - other.x, self.y - other.y)
346346+347347+ def __mul__(self, n):
348348+ return Point(self.x * n, self.y * n)
349349+350350+ def __div__(self, n):
351351+ return Point(self.x / n, self.y / n)
352352+353353+ def __neg__(self):
354354+ return Point(-self.x, -self.y)
355355+356356+ def __eq__(self, other):
357357+ return self.x == other.x and self.y == other.y
358358+359359+ def __ne__(self, other):
360360+ return not self == other
361361+362362+ def __lt__(self, other):
363363+ return self.length < other.length
364364+365365+ def __str__(self):
366366+ return "({}, {})".format(self.x, self.y)
367367+368368+ def __repr__(self):
369369+ return "Point({}, {})".format(self.x, self.y)
370370+371371+ def __hash__(self):
372372+ return hash(tuple((self.x, self.y)))
373373+374374+ def dist(self, other):
375375+ return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
376376+377377+ def dist_manhattan(self, other):
378378+ return abs(self.x - other.x) + abs(self.y - other.y)
379379+380380+ def angle(self, to=None):
381381+ if to is None:
382382+ return math.atan2(self.y, self.x)
383383+ return math.atan2(self.y - to.y, self.x - to.x)
384384+385385+ def rotate(self, turns):
386386+ """Returns the rotation of the Point around (0, 0) `turn` times clockwise."""
387387+ turns = turns % 4
388388+389389+ if turns == 1:
390390+ return Point(self.y, -self.x)
391391+ elif turns == 2:
392392+ return Point(-self.x, -self.y)
393393+ elif turns == 3:
394394+ return Point(-self.y, self.x)
395395+ else:
396396+ return self
397397+398398+ @property
399399+ def manhattan(self):
400400+ return abs(self.x) + abs(self.y)
401401+402402+ @property
403403+ def length(self):
404404+ return math.sqrt(self.x ** 2 + self.y ** 2)
405405+406406+ def neighbours_4(self):
407407+ return [self + p for p in DIRS_4]
408408+409409+ def neighbors_4(self):
410410+ return self.neighbours_4()
411411+412412+ def neighbours(self):
413413+ return self.neighbours_4()
414414+415415+ def neighbors(self):
416416+ return self.neighbours()
417417+418418+ def neighbours_8(self):
419419+ return [self + p for p in DIRS_8]
420420+421421+ def neighbors_8(self):
422422+ return self.neighbours_8()
423423+424424+425425+DIRS_4 = DIRS = [
426426+ Point(0, 1), # north
427427+ Point(1, 0), # east
428428+ Point(0, -1), # south
429429+ Point(-1, 0), # west
430430+]
431431+432432+DIRS_8 = [
433433+ Point(0, 1), # N
434434+ Point(1, 1), # NE
435435+ Point(1, 0), # E
436436+ Point(1, -1), # SE
437437+ Point(0, -1), # S
438438+ Point(-1, -1), # SW
439439+ Point(-1, 0), # W
440440+ Point(-1, 1), # NW
441441+]
+7-2
README.md
···11-# advent
11+# iKevinY/advent
2233My [Advent of Code](https://adventofcode.com) solutions.
445566-### Test Runner
66+## Test Runner
7788Under the request of the Advent of Code team, puzzle inputs and outputs
99are not being committed to this repo. However, I have written a small
···1414`YYYY/outputs/DD.txt`. If both files are present, the file will be
1515tested against the input; it passes if all lines in the output file
1616are printed to stdout during the execution of the program.
1717+1818+## License
1919+2020+Code in the `2015`, `2016`, `2017`, and `2018` directories are licensed
2121+under the [MIT License](LICENSE).