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.

Add files for 2021

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