├── 2015 ├── 01.py ├── 02.py ├── 03.py ├── 04.py ├── 05.py ├── 06.py ├── 07.py ├── 08.py ├── 09.py ├── 10.py ├── 11.py ├── 12.py ├── 13.py ├── 14.py ├── 15.py ├── 16.py ├── 17.py ├── 18.py └── 19.py ├── 2016 ├── 01.py ├── 02.py ├── 03.py ├── 04.py ├── 05.py ├── 06.py └── 07.py ├── 2017 ├── 01.py └── 02.py ├── 2018 ├── 01.py └── 02.py ├── 2019 ├── 01a.py ├── 01b.py ├── 02a.py ├── 02b.py ├── 03a.py ├── 03b.py ├── 04a.py ├── 04b.py ├── 05a.py ├── 05b.py ├── 06a.py ├── 06b.py ├── 07a.py ├── 07b.py ├── 08a.py ├── 08b.py ├── 09.py ├── 10.py ├── 11.py ├── 12.py └── 13.py ├── 2020 ├── 01.py ├── 02.py ├── 03.py ├── 04.py ├── 05.py ├── 06.py ├── 07.py ├── 08.py ├── 09.py ├── 10.py ├── 11.py ├── 12.py ├── 13.py ├── 14.py ├── 15.py ├── 16.py ├── 17.py ├── 18.py ├── 19.py ├── 20.py ├── 21.py ├── 22.py ├── 23.py ├── 24.py └── 25.py ├── 2021 ├── 01.py ├── 02.py ├── 03.py ├── 04.py ├── 05.py ├── 06.py ├── 07.py ├── 08.py ├── 09.py ├── 10.py ├── 11.py ├── 12.py ├── 13.py ├── 14.py ├── 15.py ├── 16.py └── 17.py ├── 2022 ├── 01.py ├── 02.py ├── 03.py ├── 04.py ├── 05.py ├── 06.py ├── 07.py ├── 08.py ├── 09.py ├── 10.py ├── 11.py ├── 12.py ├── 13.py ├── 14.py ├── 21.py ├── 23.py ├── 24.py └── 25.py ├── 2023 ├── 01.py ├── 02.py ├── 03.py ├── 04.py └── 05.py ├── 2024 ├── 01.py └── 02.py ├── .gitignore ├── .pre-commit-config.yaml ├── README.md └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: trailing-whitespace 7 | types: 8 | - python 9 | - id: end-of-file-fixer 10 | types: 11 | - python 12 | - id: check-added-large-files 13 | types: 14 | - python 15 | - id: check-yaml 16 | types: 17 | - yaml 18 | - repo: https://github.com/psf/black 19 | rev: 24.10.0 20 | hooks: 21 | - id: black 22 | -------------------------------------------------------------------------------- /2015/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | floor, basement = 0, None 3 | 4 | for i, c in enumerate(input()): 5 | floor += 1 if c == "(" else -1 6 | if floor == -1 and basement is None: 7 | basement = i + 1 8 | 9 | print(floor) 10 | print(basement) 11 | -------------------------------------------------------------------------------- /2015/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | paper = ribbon = 0 3 | 4 | for line in open(0): 5 | a, b, c = sorted(map(int, line.split("x"))) 6 | paper += 2 * (a * b + b * c + c * a) + a * b 7 | ribbon += 2 * (a + b) + a * b * c 8 | 9 | print(paper) 10 | print(ribbon) 11 | -------------------------------------------------------------------------------- /2015/03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | orientation = {"^": 1j, "v": -1j, ">": 1, "<": -1} 3 | location, locations = 0, [0, 0] 4 | houses1, houses2 = set((location,)), set(locations) 5 | 6 | for i, c in enumerate(input()): 7 | location += orientation[c] 8 | houses1.add(location) 9 | 10 | locations[(j := i % 2)] += orientation[c] 11 | houses2.add(locations[j]) 12 | 13 | print(len(houses1)) 14 | print(len(houses2)) 15 | -------------------------------------------------------------------------------- /2015/04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from hashlib import md5 3 | from itertools import count 4 | 5 | key, h5, h6 = input(), 0, 0 6 | 7 | for i in count(1): 8 | h = md5(f"{key}{i}".encode()).hexdigest() 9 | # The nested conditions speed up evaluation 10 | if h.startswith("00000"): 11 | if not h5: 12 | h5 = i 13 | if h6: 14 | break 15 | if not h6 and h.startswith("000000"): 16 | h6 = i 17 | if h5: 18 | break 19 | 20 | print(h5) 21 | print(h6) 22 | -------------------------------------------------------------------------------- /2015/05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | 4 | # fmt: off 5 | r1 = re.compile( 6 | r"^" 7 | r"(?=(.*[aeiou]){3})" 8 | r"(?=.*(?P.)(?P=twin))" 9 | r"(?!.*(ab|cd|pq|xy))" 10 | ) 11 | r2 = re.compile( 12 | r"^" 13 | r"(?=.*(?P..).*(?P=twins))" 14 | r"(?=.*(?P.).(?P=repeat))" 15 | ) 16 | # fmt: on 17 | lines = open(0).read().splitlines() 18 | for r in r1, r2: 19 | print(sum(bool(r.search(line)) for line in lines)) 20 | -------------------------------------------------------------------------------- /2015/06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | import re 3 | from array import array 4 | 5 | r = re.compile(r"(.+) (\d+),(\d+) through (\d+),(\d+)") 6 | lights1 = array("B", [0] * 1_000_000) 7 | lights2 = array("I", [0] * 1_000_000) 8 | 9 | for line in open(0): 10 | action, *numbers = r.match(line).groups() 11 | col_min, row_min, col_max, row_max = map(int, numbers) 12 | 13 | for row in range(row_min, row_max + 1): 14 | for col in range(col_min, col_max + 1): 15 | bulb = row * 1000 + col 16 | if action == "turn on": 17 | lights1[bulb] = 1 18 | lights2[bulb] += 1 19 | elif action == "turn off": 20 | lights1[bulb] = 0 21 | if lights2[bulb] > 0: 22 | lights2[bulb] -= 1 23 | else: # action == "toggle": 24 | lights1[bulb] = 0 if lights1[bulb] else 1 25 | lights2[bulb] += 2 26 | 27 | print(sum(lights1)) 28 | print(sum(lights2)) 29 | -------------------------------------------------------------------------------- /2015/07.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from functools import cache 3 | from operator import iand, ior, lshift, rshift 4 | 5 | 6 | @cache 7 | def solve(wire): 8 | if wire.isdigit(): 9 | return int(wire) 10 | 11 | ins = wires[wire] 12 | 13 | if len(ins) == 3: 14 | return ops[ins[1]](solve(ins[0]), solve(ins[2])) 15 | elif len(ins) == 2: 16 | return ~solve(ins[1]) & 65535 17 | else: 18 | return solve(ins[0]) 19 | 20 | 21 | ops = {"AND": iand, "OR": ior, "RSHIFT": rshift, "LSHIFT": lshift} 22 | # fmt: off 23 | wires = { 24 | wire: ins.split() 25 | for line in open(0) 26 | for ins, wire in [line.strip().split(" -> ")] 27 | } 28 | # fmt: on 29 | print(a := solve("a")) 30 | wires["b"] = [str(a)] 31 | solve.cache_clear() 32 | print(solve("a")) 33 | -------------------------------------------------------------------------------- /2015/08.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | s1 = s2 = 0 3 | 4 | for line in open(0).read().splitlines(): 5 | s1 += len(line) - len(eval(line)) 6 | s2 += 2 + line.count("\\") + line.count('"') 7 | 8 | print(s1) 9 | print(s2) 10 | -------------------------------------------------------------------------------- /2015/09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import defaultdict 3 | from itertools import permutations 4 | 5 | cities = defaultdict(dict) 6 | 7 | for city_from, _, city_to, _, distance in map(str.split, open(0).read().splitlines()): 8 | cities[city_from][city_to] = int(distance) 9 | cities[city_to][city_from] = int(distance) 10 | 11 | paths = [ 12 | # fmt: off 13 | sum( 14 | cities[city_from][city_to] 15 | for city_from, city_to in zip(locations, locations[1:]) 16 | ) 17 | # fmt: on 18 | for locations in permutations(cities) 19 | ] 20 | 21 | print(min(paths)) 22 | print(max(paths)) 23 | -------------------------------------------------------------------------------- /2015/10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import defaultdict 3 | 4 | 5 | def solve(numbers, repeats): 6 | for _ in range(repeats): 7 | new_numbers, count, previous = [], 1, numbers[0] 8 | 9 | for current in numbers[1:]: 10 | if previous == current: 11 | count += 1 12 | else: 13 | new_numbers.extend((count, previous)) 14 | count, previous = 1, current 15 | 16 | new_numbers.extend((count, previous)) 17 | numbers = new_numbers 18 | 19 | return numbers 20 | 21 | 22 | data = input() 23 | numbers = list(map(int, data)) 24 | 25 | print(len(numbers := solve(numbers, 40))) 26 | print(len(solve(numbers, 10))) 27 | 28 | 29 | # Alternative 30 | 31 | # Source: http://www.njohnston.ca/2010/10/a-derivation-of-conways-degree-71-look-and-say-polynomial/ 32 | # fmt: off 33 | elements = ["1112", "1112133", "111213322112", "111213322113", "1113", "11131", "111311222112", "111312", "11131221", "1113122112", "1113122113", "11131221131112", "111312211312", "11131221131211", "111312211312113211", "111312211312113221133211322112211213322112", "111312211312113221133211322112211213322113", "11131221131211322113322112", "11131221133112", "1113122113322113111221131221", "11131221222112", "111312212221121123222112", "111312212221121123222113", "11132", "1113222", "1113222112", "1113222113", "11133112", "12", "123222112", "123222113", "12322211331222113112211", "13", "131112", "13112221133211322112211213322112", "13112221133211322112211213322113", "13122112", "132", "13211", "132112", "1321122112", "132112211213322112", "132112211213322113", "132113", "1321131112", "13211312", "1321132", "13211321", "132113212221", "13211321222113222112", "1321132122211322212221121123222112", "1321132122211322212221121123222113", "13211322211312113211", "1321133112", "1322112", "1322113", "13221133112", "1322113312211", "132211331222113112211", "13221133122211332", "22", "3", "3112", "3112112", "31121123222112", "31121123222113", "3112221", "3113", "311311", "31131112", "3113112211", "3113112211322112", "3113112211322112211213322112", "3113112211322112211213322113", "311311222", "311311222112", "311311222113", "3113112221131112", "311311222113111221", "311311222113111221131221", "31131122211311122113222", "3113112221133112", "311312", "31132", "311322113212221", "311332", "3113322112", "3113322113", "312", "312211322212221121123222113", "312211322212221121123222112", "32112"] 34 | evolution = [[62], [63, 61], [64], [65], [67], [68], [83, 54], [69], [70], [75], [76], [81], [77], [78], [79], [80, 28, 90], [80, 28, 89], [80, 29], [74, 28, 91], [74, 31], [71], [72], [73], [82], [85], [86], [87], [88, 91], [0], [2], [3], [1, 60, 28, 84], [4], [27], [23, 32, 60, 28, 90], [23, 32, 60, 28, 89], [6], [7], [8], [9], [20], [21], [22], [10], [18], [11], [12], [13], [14], [17], [15], [16], [19], [5, 60, 28, 91], [25], [26], [24, 28, 91], [24, 28, 66], [24, 28, 84], [24, 28, 67, 60, 28, 88], [60], [32], [39], [40], [41], [42], [37, 38], [43], [47], [53], [48], [49], [50], [51], [46, 37], [46, 54], [46, 55], [46, 56], [46, 57], [46, 58], [46, 59], [46, 32, 60, 28, 91], [44], [45], [52], [37, 28, 88], [37, 29], [37, 30], [33], [35], [34], [36]] 35 | # fmt: on 36 | 37 | 38 | def solve_njohnston(sequence, repeats): 39 | for _ in range(repeats): 40 | new_sequence = defaultdict(int) 41 | 42 | for element, count in sequence.items(): 43 | for new_element in evolution[element]: 44 | new_sequence[new_element] += count 45 | 46 | sequence = new_sequence 47 | 48 | return sequence, sum( 49 | # fmt: off 50 | len(elements[element]) * count 51 | for element, count in sequence.items() 52 | # fmt: on 53 | ) 54 | 55 | 56 | sequence, length = solve_njohnston({elements.index(data): 1}, 40) 57 | print(length) 58 | print(solve_njohnston(sequence, 10)[1]) 59 | -------------------------------------------------------------------------------- /2015/11.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from itertools import product 4 | 5 | 6 | def solve(password): 7 | found = None 8 | 9 | for i in range(1, len(password) + 1): 10 | if password[-i] == letters[-1]: 11 | next 12 | 13 | prefix = password[:-i] 14 | 15 | for suffix in product(letters[letters.index(password[-i]) + 1 :], *([letters] * (i - 1))): 16 | candidate = prefix + "".join(suffix) 17 | if r.search(candidate) and any( 18 | candidate[j : j + 3] in triplets for j in range(len(candidate) - 2) 19 | ): 20 | found = candidate 21 | break 22 | 23 | if found is not None: 24 | break 25 | 26 | return found 27 | 28 | 29 | letters = "abcdefghjkmnpqrstuvwxyz" 30 | triplets = set("".join(triplet) for triplet in zip(letters, letters[1:], letters[2:])) 31 | r = re.compile(rf"([{letters}])\1.*([{letters}])\2") 32 | 33 | print(password := solve(input())) 34 | print(solve(password)) 35 | -------------------------------------------------------------------------------- /2015/12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | def solve(data, ignore=None): 3 | if isinstance(data, int): 4 | return data 5 | elif isinstance(data, list): 6 | return sum(solve(d, ignore) for d in data) 7 | elif isinstance(data, dict) and ignore not in data.values(): 8 | return sum(solve(d, ignore) for d in data.values()) 9 | 10 | return 0 11 | 12 | 13 | print(solve(data := eval(input()))) 14 | print(solve(data, "red")) 15 | -------------------------------------------------------------------------------- /2015/13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from math import factorial 3 | from itertools import permutations 4 | from collections import defaultdict 5 | 6 | 7 | def solve(first, people): 8 | limit = factorial(len(people)) // 2 9 | 10 | return max( 11 | sum(data[frozenset([a, b])] for a, b in zip(first + table, table + first)) 12 | for i, table in enumerate(permutations(people)) 13 | if i < limit # avoid cyclic permutations and reversals 14 | ) 15 | 16 | 17 | people, data = set(), defaultdict(int) 18 | 19 | for a, _, gain, n, *_, b in map(str.split, open(0).read().splitlines()): 20 | people.add(a) 21 | data[frozenset([a, b[:-1]])] += int(n) * (-1) ** (gain != "gain") 22 | 23 | first = people.pop() 24 | print(solve(tuple([first]), people)) 25 | 26 | people.add(first) 27 | print(solve(tuple("0"), people)) 28 | -------------------------------------------------------------------------------- /2015/14.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | def race(reindeers, s): 3 | # fmt: off 4 | return max( 5 | (speed * ( s // (fly + rest) * fly + min(fly, s % (fly + rest))), i, ) 6 | for i, (speed, fly, rest) in enumerate(reindeers) 7 | ) 8 | # fmt: on 9 | 10 | 11 | reindeers = [ 12 | # fmt: off 13 | list(map(int, (speed, fly, rest))) 14 | for _, _, _, speed, _, _, fly, *_, rest, _ in map(str.split, open(0).read().splitlines()) 15 | # fmt: on 16 | ] 17 | winners = [0] * len(reindeers) 18 | 19 | print(race(reindeers, 2503)[0]) 20 | 21 | for s in range(1, 2503 + 2): 22 | winners[race(reindeers, s)[1]] += 1 23 | 24 | print(max(winners)) 25 | -------------------------------------------------------------------------------- /2015/15.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from math import prod 4 | 5 | 6 | def divide(number, buckets): 7 | if buckets == 1: 8 | yield (number,) 9 | else: 10 | for bucket in range(number + 1): 11 | for rest in divide(number - bucket, buckets - 1): 12 | yield (bucket,) + rest 13 | 14 | 15 | ingredients = [tuple(map(int, re.findall(r"-?\d+", line))) for line in open(0)] 16 | s1 = s2 = 0 17 | 18 | for buckets in divide(100, len(ingredients)): 19 | # fmt:off 20 | *props, cals = ( 21 | max(sum(b * s for b, s in zip(buckets, slice)), 0) 22 | for slice in zip(*ingredients) 23 | ) 24 | # fmt:on 25 | if s1 < (total := prod(props)): 26 | s1 = total 27 | if cals == 500 and s2 < total: 28 | s2 = total 29 | 30 | print(s1) 31 | print(s2) 32 | -------------------------------------------------------------------------------- /2015/16.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # fmt: off 3 | def solve(lines, ticker): 4 | return next( 5 | sue 6 | for sue, line in enumerate(lines, start=1) 7 | if all(ticker[key](value) for key, value in line.items()) 8 | ) 9 | 10 | 11 | ticker = { 12 | "children": lambda a: a == 3, 13 | "cats": lambda a: a == 7, 14 | "samoyeds": lambda a: a == 2, 15 | "pomeranians": lambda a: a == 3, 16 | "akitas": lambda a: a == 0, 17 | "vizslas": lambda a: a == 0, 18 | "goldfish": lambda a: a == 5, 19 | "trees": lambda a: a == 3, 20 | "cars": lambda a: a == 2, 21 | "perfumes": lambda a: a == 1, 22 | } 23 | 24 | lines = [ 25 | { 26 | l[i]: int(l[i + 1]) 27 | for l in [line.replace(":", "").replace(",", "").split()] 28 | for i in range(2, len(l), 2) 29 | } 30 | for line in open(0).read().splitlines() 31 | ] 32 | 33 | print(solve(lines, ticker)) 34 | 35 | ticker["cats"] = lambda a: a > 7 36 | ticker["trees"] = lambda a: a > 3 37 | ticker["pomeranians"] = lambda a: a < 3 38 | ticker["goldfish"] = lambda a: a < 5 39 | 40 | print(solve(lines, ticker)) 41 | -------------------------------------------------------------------------------- /2015/17.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from itertools import combinations 3 | 4 | containers = list(map(int, open(0).read().splitlines())) 5 | # fmt: off 6 | k = [ 7 | sum( 8 | sum(comb) == 150 9 | for comb in combinations(containers, i) 10 | ) 11 | for i in range(2, len(containers) + 1) 12 | ] 13 | # fmt: on 14 | print(sum(k)) 15 | print(next(i for i in k if i > 0)) 16 | -------------------------------------------------------------------------------- /2015/18.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | def solve(t, size, corners): 3 | for _ in range(100): 4 | t = { 5 | light 6 | for r in range(size) 7 | for c in range(size) 8 | for light in [complex(r, c)] 9 | # fmt: off 10 | for neighbors in [sum( 11 | light + d in t 12 | for d in (1, -1, 1j, -1j, 1+1j, 1-1j, -1+1j, -1-1j) 13 | )] 14 | # fmt: on 15 | if neighbors == 3 or (neighbors == 2 and light in t) or light in corners 16 | } 17 | 18 | return len(t) 19 | 20 | 21 | # fmt: off 22 | t = { 23 | complex(r, c) 24 | for r, line in enumerate(open(0)) 25 | for c, s in enumerate(line.strip()) 26 | if s == "#" 27 | } 28 | # fmt: on 29 | size = int(max(i.real for i in t)) + 1 30 | corners = {0, size - 1, complex(0, size - 1), complex(size - 1, size - 1)} 31 | 32 | print(solve(t.copy(), size, set())) 33 | print(solve(t | corners, size, corners)) 34 | -------------------------------------------------------------------------------- /2015/19.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import defaultdict 3 | import re 4 | 5 | data, mol = open(0).read().split("\n\n") 6 | reps, mol = defaultdict(list), mol.strip() 7 | for line in data.splitlines(): 8 | src, dst = line.split(" => ") 9 | reps[src].append(dst) 10 | 11 | combs, i = set(), 0 12 | while i < len(mol): 13 | for size in (2, 1): 14 | if (j := i + size) - 1 < len(mol) and (m := mol[i:j]) in reps: 15 | for r in reps[m]: 16 | combs.add(mol[:i] + r + mol[j:]) 17 | i += size 18 | break 19 | else: 20 | i += 1 21 | 22 | print(len(combs)) 23 | 24 | # Kudos to https://www.reddit.com/r/adventofcode/comments/3xflz8/comment/cy4etju 25 | print( 26 | len(re.findall(r"[A-Z]", mol)) 27 | - 2 * len(re.findall(r"Rn", mol)) 28 | - 2 * len(re.findall(r"Y", mol)) 29 | - 1 30 | ) 31 | -------------------------------------------------------------------------------- /2016/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | t = {"R": +1, "L": -1} # +90 right, -90 left 5 | d = [+1j, 1, -1j, -1] # North, East, South, West 6 | di = 0 7 | p = 0 # position 8 | 9 | ls = {p} # locations 10 | lo = None 11 | 12 | for s in [(t[i[0]], int(i[1:])) for i in next(fileinput.input()).strip().split(", ")]: 13 | di = (di + s[0]) % len(d) 14 | 15 | for i in range(s[1]): 16 | p += d[di] 17 | if lo is None and p in ls: 18 | lo = p 19 | ls.add(p) 20 | 21 | print(abs(p.real) + abs(p.imag)) 22 | print(abs(lo.real) + abs(lo.imag)) 23 | -------------------------------------------------------------------------------- /2016/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | lines = [i.strip() for i in fileinput.input()] 5 | 6 | d = {"U": -1, "D": +1, "L": -1j, "R": +1j} 7 | p = 1 + 1j 8 | 9 | for line in lines: 10 | for char in line: 11 | t = p + d[char] 12 | if 0 <= t.real <= 2 and 0 <= t.imag <= 2: 13 | p = t 14 | print(int(p.real * 3 + p.imag + 1), end="") 15 | print() 16 | 17 | m = [ 18 | [0, 0, 1, 0, 0], 19 | [0, 2, 3, 4, 0], 20 | [5, 6, 7, 8, 9], 21 | [0, "A", "B", "C", 0], 22 | [0, 0, "D", 0, 0], 23 | ] 24 | p = 2 + 0j 25 | 26 | for line in lines: 27 | for char in line: 28 | t = p + d[char] 29 | # fmt: off 30 | if 0 <= t.real < len(m) and \ 31 | 0 <= t.imag < len(m[int(t.real)]) and \ 32 | m[int(t.real)][int(t.imag)] != 0: 33 | p = t 34 | # fmt: on 35 | print(m[int(p.real)][int(p.imag)], end="") 36 | print() 37 | -------------------------------------------------------------------------------- /2016/03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | count = 0 5 | l = [[], [], []] 6 | 7 | for line in fileinput.input(): 8 | a, b, c = [int(i) for i in line.strip().split()] 9 | if a + b > c and a + c > b and b + c > a: 10 | count += 1 11 | l[0].append(a) 12 | l[1].append(b) 13 | l[2].append(c) 14 | 15 | print(count) 16 | 17 | count = 0 18 | 19 | for i in l: 20 | for j in range(0, len(i), 3): 21 | a, b, c = i[j : j + 3] 22 | if a + b > c and a + c > b and b + c > a: 23 | count += 1 24 | 25 | print(count) 26 | -------------------------------------------------------------------------------- /2016/04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | import fileinput 4 | from collections import Counter 5 | 6 | 7 | def descrypt(c, r): 8 | if c == "-": 9 | return " " 10 | 11 | return chr((ord(c) - ord("a") + r) % 26 + ord("a")) 12 | 13 | 14 | s = 0 15 | nos = None 16 | 17 | for line in fileinput.input(): 18 | name, sector, checksum = re.match(r"^(.*)-(\d+)\[(.+)\]$", line.strip()).groups() 19 | sector = int(sector) 20 | 21 | if "northpole" in "".join([descrypt(c, sector) for c in name]): 22 | nos = sector 23 | 24 | name = "".join(sorted(name.replace("-", ""))) 25 | room = "".join([i[0] for i in Counter(name).most_common(len(checksum))]) 26 | 27 | if room == checksum: 28 | s += sector 29 | 30 | print(s) 31 | print(nos) 32 | -------------------------------------------------------------------------------- /2016/05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from hashlib import md5 4 | from itertools import count 5 | 6 | c1 = c2 = 8 7 | t1 = "" 8 | t2 = [" "] * c2 9 | 10 | for i in count(1): 11 | hash = md5((sys.argv[1] + str(i)).encode("ascii")).hexdigest() 12 | if hash[:5] == "00000": 13 | if c1 > 0: 14 | c1 -= 1 15 | t1 += hash[5] 16 | 17 | if c2 > 0 and hash[5].isdigit(): 18 | j = int(hash[5]) 19 | if 0 <= j < len(t2) and t2[j] == " ": 20 | c2 -= 1 21 | t2[j] = hash[6] 22 | 23 | if c1 <= 0 and c2 <= 0: 24 | break 25 | 26 | print(t1) 27 | print("".join(t2)) 28 | -------------------------------------------------------------------------------- /2016/06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from collections import Counter 4 | 5 | m = None 6 | 7 | for line in fileinput.input(): 8 | line = line.strip() 9 | 10 | if m is None: 11 | m = [Counter() for _ in range(len(line))] 12 | 13 | for i, c in enumerate(line): 14 | m[i][c] += 1 15 | 16 | most = "" 17 | least = "" 18 | 19 | for n in m: 20 | most += n.most_common(1)[0][0] 21 | least += n.most_common()[-1][0] 22 | 23 | print(most) 24 | print(least) 25 | -------------------------------------------------------------------------------- /2016/07.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | import fileinput 4 | 5 | tls_count = 0 6 | ssl_count = 0 7 | 8 | for line in fileinput.input(): 9 | line = line.strip() 10 | 11 | tls = False 12 | hypernet = False 13 | for s in re.split(r"([^\[\]]+)", line): 14 | if s == "[": 15 | hypernet = True 16 | elif s == "]": 17 | hypernet = False 18 | elif len(s): 19 | match = re.search(r"(.)(?!\1)(.)\2\1", s) 20 | if match: 21 | if hypernet: 22 | tls = False 23 | break 24 | else: 25 | tls = True 26 | if tls: 27 | tls_count += 1 28 | 29 | # fmt: off 30 | if re.search(r"""([^\[\]])(?!\1)([^\[\]])\1 # ABA 31 | [^\[\]]* # * 32 | (?:\[[^\[\]]*\][^\[\]]*)* # optional [*]* sections 33 | \[ # [ 34 | [^\[\]]* # * 35 | \2\1\2 # BAB""", line, re.X) or \ 36 | re.search(r"""([^\[\]])(?!\1)([^\[\]])\1 # ABA 37 | [^\[\]]* # * 38 | \] # ] 39 | (?:[^\[\]]*\[[^\[\]]*\])* # optional *[*] sections 40 | [^\[\]]* # * 41 | \2\1\2 # BAB""", line, re.X): 42 | ssl_count += 1 43 | # fmt: on 44 | 45 | print(tls_count) 46 | print(ssl_count) 47 | -------------------------------------------------------------------------------- /2017/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | c = [int(i) for i in input()] 3 | print(sum(c[i] for i in range(len(c)) if c[i] == c[i - 1])) 4 | n = len(c) // 2 5 | print(sum(c[i] * 2 for i in range(n) if c[i] == c[i - n])) 6 | -------------------------------------------------------------------------------- /2017/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # fmt:off 3 | s = [ 4 | [int(i) for i in l.split()] 5 | for l in open(0).read().splitlines() 6 | ] 7 | print(sum(max(r) - min(r) for r in s)) 8 | print(sum( 9 | a // b 10 | for r in s 11 | for a in r 12 | for b in r 13 | if a != b and a % b == 0 14 | )) 15 | -------------------------------------------------------------------------------- /2018/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from itertools import accumulate, cycle 4 | 5 | n = list(map(int, fileinput.input())) 6 | print(sum(n)) 7 | 8 | seen = set([0]) 9 | print(next(f for f in accumulate(cycle(n)) if f in seen or seen.add(f))) 10 | -------------------------------------------------------------------------------- /2018/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from collections import Counter 4 | from itertools import combinations 5 | 6 | lines, two, three = [line.strip() for line in fileinput.input()], 0, 0 7 | 8 | for line in lines: 9 | r = Counter(line).values() 10 | if 2 in r: 11 | two += 1 12 | if 3 in r: 13 | three += 1 14 | 15 | print(two * three) 16 | 17 | for a, b in combinations(lines, 2): 18 | d = None 19 | for i, x, y in zip(range(len(a)), a, b): 20 | if x != y: 21 | if d is not None: 22 | break 23 | d = i 24 | else: # no break 25 | print(a[:d] + a[d + 1 :]) 26 | exit 27 | -------------------------------------------------------------------------------- /2019/01a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | sum = 0 5 | 6 | for line in fileinput.input(): 7 | sum += int(int(line.strip()) / 3) - 2 8 | 9 | print(sum) 10 | -------------------------------------------------------------------------------- /2019/01b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | 5 | def fuel(mass): 6 | fuel = 0 7 | 8 | # while mass > 8: 9 | # mass = int(mass / 3) - 2 10 | # fuel += mass 11 | while True: 12 | mass = int(mass / 3) - 2 13 | if mass <= 0: 14 | break 15 | fuel += mass 16 | 17 | return fuel 18 | 19 | 20 | sum = 0 21 | 22 | for line in fileinput.input(): 23 | sum += fuel(int(line.strip())) 24 | 25 | print(sum) 26 | -------------------------------------------------------------------------------- /2019/02a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | # Split comma separated strings and convert them to numbers 5 | list = [int(i) for i in next(fileinput.input()).strip().split(",")] 6 | 7 | list[1] = 12 8 | list[2] = 2 9 | 10 | i = 0 11 | while i < len(list): 12 | if list[i] == 1: 13 | list[list[i + 3]] = list[list[i + 1]] + list[list[i + 2]] 14 | i += 4 15 | elif list[i] == 2: 16 | list[list[i + 3]] = list[list[i + 1]] * list[list[i + 2]] 17 | i += 4 18 | elif list[i] == 99: 19 | break 20 | 21 | print(list[0]) 22 | -------------------------------------------------------------------------------- /2019/02b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | 5 | def calculate(list, noun, verb): 6 | list[1] = noun 7 | list[2] = verb 8 | 9 | i = 0 10 | while i < len(list): 11 | if list[i] == 1: 12 | list[list[i + 3]] = list[list[i + 1]] + list[list[i + 2]] 13 | i += 4 14 | elif list[i] == 2: 15 | list[list[i + 3]] = list[list[i + 1]] * list[list[i + 2]] 16 | i += 4 17 | elif list[i] == 99: 18 | break 19 | 20 | return list[0] 21 | 22 | 23 | # Split comma separated strings and convert them to numbers 24 | list = [int(i) for i in next(fileinput.input()).strip().split(",")] 25 | 26 | for i in range(100): 27 | for j in range(100): 28 | # Using list slicing to clone the list and prevent modification 29 | # of the original 30 | if calculate(list[:], i, j) == 19690720: 31 | print(100 * i + j) 32 | break 33 | -------------------------------------------------------------------------------- /2019/03a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | 5 | def positions(path): 6 | x = y = 0 7 | for p in path: 8 | for i in range(int(p[1:])): 9 | if p[0] == "U": 10 | y += 1 11 | elif p[0] == "D": 12 | y -= 1 13 | elif p[0] == "R": 14 | x += 1 15 | elif p[0] == "L": 16 | x -= 1 17 | # print(f"{p} {p[0]} {int(p[1:])} {x} {y}") 18 | yield (x, y) 19 | 20 | 21 | input = iter(fileinput.input()) 22 | wire1 = next(input).strip().split(",") 23 | wire2 = next(input).strip().split(",") 24 | 25 | unique_positions1 = set(positions(wire1)) 26 | unique_positions2 = set(positions(wire2)) 27 | crossings = unique_positions1.intersection(unique_positions2) 28 | 29 | min_distance = None 30 | for i in crossings: 31 | distance = abs(i[0]) + abs(i[1]) 32 | # print(f"{i} {distance}") 33 | if min_distance is None or min_distance > distance: 34 | min_distance = distance 35 | 36 | print(min_distance) 37 | -------------------------------------------------------------------------------- /2019/03b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | 5 | def positions(path): 6 | x = y = 0 7 | for p in path: 8 | for i in range(int(p[1:])): 9 | if p[0] == "U": 10 | y += 1 11 | elif p[0] == "D": 12 | y -= 1 13 | elif p[0] == "R": 14 | x += 1 15 | elif p[0] == "L": 16 | x -= 1 17 | # print(f"{p} {p[0]} {int(p[1:])} {x} {y}") 18 | yield (x, y) 19 | 20 | 21 | input = iter(fileinput.input()) 22 | wire1 = next(input).strip().split(",") 23 | wire2 = next(input).strip().split(",") 24 | 25 | unique_positions1 = set(positions(wire1)) 26 | unique_positions2 = set(positions(wire2)) 27 | crossings = unique_positions1.intersection(unique_positions2) 28 | 29 | min_steps = None 30 | for i in crossings: 31 | steps = 0 32 | 33 | for p in positions(wire1): 34 | steps += 1 35 | if p == i: 36 | break 37 | 38 | for p in positions(wire2): 39 | steps += 1 40 | if p == i: 41 | break 42 | 43 | # print(f"{i} {steps}") 44 | if min_steps is None or min_steps > steps: 45 | min_steps = steps 46 | 47 | print(min_steps) 48 | -------------------------------------------------------------------------------- /2019/04a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | start, end = 264360, 746325 5 | 6 | if len(sys.argv) == 2: 7 | start, end = [int(x) for x in sys.argv[1].strip().split("-")] 8 | 9 | count = 0 10 | 11 | while start <= end: 12 | previous = None 13 | double = False 14 | nondecrease = True 15 | 16 | for i, c in enumerate(str(start)): 17 | c = int(c) 18 | if i > 0: 19 | if previous == c: 20 | double = True 21 | elif previous > c: 22 | nondecrease = False 23 | break 24 | previous = c 25 | 26 | if double and nondecrease: 27 | # print(f"{count}: {start} {end}") 28 | count += 1 29 | 30 | start += 1 31 | 32 | print(count) 33 | -------------------------------------------------------------------------------- /2019/04b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | start, end = 264360, 746325 5 | 6 | if len(sys.argv) == 2: 7 | start, end = [int(x) for x in sys.argv[1].strip().split("-")] 8 | 9 | count = 0 10 | 11 | while start <= end: 12 | previous = None 13 | duplicate = 0 14 | double = False 15 | nondecrease = True 16 | 17 | for i, c in enumerate(str(start)): 18 | c = int(c) 19 | 20 | if i > 0: 21 | if previous > c: 22 | nondecrease = False 23 | break 24 | elif previous == c: 25 | duplicate += 1 26 | else: # previous < c 27 | if duplicate == 1: 28 | double = True 29 | duplicate = 0 30 | 31 | previous = c 32 | 33 | if nondecrease and (double or duplicate == 1): 34 | # print(f"{count}: {start} {end}") 35 | count += 1 36 | 37 | start += 1 38 | 39 | print(count) 40 | -------------------------------------------------------------------------------- /2019/05a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | # Split comma separated strings and convert them to numbers 5 | list = [int(i) for i in next(fileinput.input()).strip().split(",")] 6 | 7 | i = 0 8 | input = 1 9 | 10 | while i < len(list): 11 | instruction = list[i] 12 | opcode = instruction % 100 13 | 14 | if opcode in (1, 2): 15 | a = list[i + 1] 16 | b = list[i + 2] 17 | 18 | if instruction % 1000 < 100: # hundreds 19 | a = list[a] 20 | 21 | if instruction % 10000 < 1000: # thousands 22 | b = list[b] 23 | 24 | list[list[i + 3]] = a + b if opcode == 1 else a * b 25 | 26 | i += 4 27 | elif opcode == 3: 28 | list[list[i + 1]] = input 29 | i += 2 30 | elif opcode == 4: 31 | a = list[i + 1] 32 | 33 | if instruction % 1000 < 100: # hundreds 34 | a = list[a] 35 | 36 | i += 2 37 | print(a) 38 | elif opcode == 99: 39 | break 40 | -------------------------------------------------------------------------------- /2019/05b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | # Split comma separated strings and convert them to numbers 5 | list = [int(i) for i in next(fileinput.input()).strip().split(",")] 6 | 7 | i = 0 8 | input = 5 9 | 10 | while i < len(list): 11 | # print(f"{i}: {list}") 12 | instruction = list[i] 13 | opcode = instruction % 100 14 | 15 | if opcode in (1, 2, 7, 8): # add, multiply, less than, equals 16 | a = list[i + 1] 17 | b = list[i + 2] 18 | 19 | if instruction % 1000 < 100: # hundreds 20 | a = list[a] 21 | 22 | if instruction % 10000 < 1000: # thousands 23 | b = list[b] 24 | 25 | if opcode == 1: 26 | list[list[i + 3]] = a + b 27 | elif opcode == 2: 28 | list[list[i + 3]] = a * b 29 | elif opcode == 7: 30 | list[list[i + 3]] = 1 if a < b else 0 31 | elif opcode == 8: 32 | list[list[i + 3]] = 1 if a == b else 0 33 | 34 | i += 4 35 | elif opcode == 3: # input 36 | list[list[i + 1]] = input 37 | i += 2 38 | elif opcode == 4: # output 39 | a = list[i + 1] 40 | 41 | if instruction % 1000 < 100: # hundreds 42 | a = list[a] 43 | 44 | i += 2 45 | print(a) 46 | elif opcode in (5, 6): # jump-if-true, jump-if-false 47 | a = list[i + 1] 48 | 49 | if instruction % 1000 < 100: # hundreds 50 | a = list[a] 51 | 52 | if (opcode == 5 and a != 0) or (opcode == 6 and a == 0): 53 | b = list[i + 2] 54 | 55 | if instruction % 10000 < 1000: # thousands 56 | b = list[b] 57 | 58 | i = b 59 | else: 60 | i += 3 61 | elif opcode == 99: 62 | break 63 | -------------------------------------------------------------------------------- /2019/06a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | map = {} 5 | 6 | for line in fileinput.input(): 7 | planet, orbit = line.strip().split(")") 8 | map[orbit] = planet 9 | 10 | orbits = 0 11 | 12 | for orbit, planet in map.items(): 13 | while True: 14 | orbits += 1 15 | if planet == "COM": 16 | break 17 | planet = map[planet] 18 | 19 | print(orbits) 20 | -------------------------------------------------------------------------------- /2019/06b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | map = {} 5 | 6 | for line in fileinput.input(): 7 | planet, orbit = line.strip().split(")") 8 | map[orbit] = planet 9 | 10 | you = {} 11 | distance = 0 12 | planet = map["YOU"] 13 | while planet != "COM": 14 | you[planet] = distance 15 | planet = map[planet] 16 | distance += 1 17 | 18 | distance = 0 19 | planet = map["SAN"] 20 | while planet not in you: 21 | planet = map[planet] 22 | distance += 1 23 | 24 | print(distance + you[planet]) 25 | -------------------------------------------------------------------------------- /2019/07a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | import itertools 4 | 5 | 6 | def compute(code_copy, inputs): 7 | code = list(code_copy) 8 | i = 0 9 | 10 | while i < len(code): 11 | # print(f"{i}: {code}") 12 | instruction = code[i] 13 | opcode = instruction % 100 14 | 15 | if opcode in (1, 2, 7, 8): # add, multiply, less than, equals 16 | a = code[i + 1] 17 | b = code[i + 2] 18 | 19 | if instruction % 1000 < 100: # hundreds 20 | a = code[a] 21 | 22 | if instruction % 10000 < 1000: # thousands 23 | b = code[b] 24 | 25 | if opcode == 1: 26 | code[code[i + 3]] = a + b 27 | elif opcode == 2: 28 | code[code[i + 3]] = a * b 29 | elif opcode == 7: 30 | code[code[i + 3]] = 1 if a < b else 0 31 | elif opcode == 8: 32 | code[code[i + 3]] = 1 if a == b else 0 33 | 34 | i += 4 35 | elif opcode == 3: # input 36 | code[code[i + 1]] = inputs.pop(0) 37 | i += 2 38 | elif opcode == 4: # output 39 | a = code[i + 1] 40 | 41 | if instruction % 1000 < 100: # hundreds 42 | a = code[a] 43 | 44 | i += 2 45 | output = a 46 | elif opcode in (5, 6): # jump-if-true, jump-if-false 47 | a = code[i + 1] 48 | 49 | if instruction % 1000 < 100: # hundreds 50 | a = code[a] 51 | 52 | if (opcode == 5 and a != 0) or (opcode == 6 and a == 0): 53 | b = code[i + 2] 54 | 55 | if instruction % 10000 < 1000: # thousands 56 | b = code[b] 57 | 58 | i = b 59 | else: 60 | i += 3 61 | elif opcode == 99: 62 | break 63 | 64 | return output 65 | 66 | 67 | # Split comma separated strings and convert them to numbers 68 | code = [int(i) for i in next(fileinput.input()).strip().split(",")] 69 | 70 | max_output = 0 71 | 72 | for phases in itertools.permutations(range(5)): 73 | output = 0 74 | for phase in phases: 75 | output = compute(code, [phase, output]) 76 | # print(phases, phase, output) 77 | if max_output < output: 78 | max_output = output 79 | 80 | print(max_output) 81 | -------------------------------------------------------------------------------- /2019/07b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | import itertools 4 | 5 | 6 | def value(c, i, indirect): 7 | x = c[i] 8 | if indirect: 9 | x = c[x] 10 | return x 11 | 12 | 13 | def compute(c, i, inp): 14 | while i < len(c): 15 | # print(f"{i}: {c}") 16 | instruction = c[i] 17 | opcode = instruction % 100 18 | 19 | if opcode in (1, 2, 7, 8): # add, multiply, less than, equals 20 | a = value(c, i + 1, instruction % 1000 < 100) # hundreds 21 | b = value(c, i + 2, instruction % 10000 < 1000) # thousands 22 | 23 | if opcode == 1: 24 | c[c[i + 3]] = a + b 25 | elif opcode == 2: 26 | c[c[i + 3]] = a * b 27 | elif opcode == 7: 28 | c[c[i + 3]] = 1 if a < b else 0 29 | elif opcode == 8: 30 | c[c[i + 3]] = 1 if a == b else 0 31 | 32 | i += 4 33 | elif opcode == 3: # input 34 | c[c[i + 1]] = inp.pop(0) 35 | i += 2 36 | elif opcode == 4: # output 37 | a = value(c, i + 1, instruction % 1000 < 100) # hundreds 38 | 39 | i += 2 40 | return i, a 41 | elif opcode in (5, 6): # jump-if-true, jump-if-false 42 | a = value(c, i + 1, instruction % 1000 < 100) # hundreds 43 | 44 | if (opcode == 5 and a != 0) or (opcode == 6 and a == 0): 45 | i = value(c, i + 2, instruction % 10000 < 1000) # thousands 46 | else: 47 | i += 3 48 | elif opcode == 99: 49 | break 50 | 51 | return None, None 52 | 53 | 54 | def amploop(phases): 55 | amp = [{"c": list(c), "i": 0, "inp": [p], "out": None} for p in phases] 56 | pos = 0 57 | out = 0 58 | 59 | while True: 60 | a = amp[pos] 61 | a["inp"].append(out) 62 | a["i"], out = compute(a["c"], a["i"], a["inp"]) 63 | if out is None: 64 | break 65 | a["out"] = out 66 | pos = (pos + 1) % 5 67 | 68 | return amp[-1]["out"] 69 | 70 | 71 | # Split comma separated strings and convert them to numbers 72 | c = [int(i) for i in next(fileinput.input()).strip().split(",")] 73 | 74 | print(max([amploop(ps) for ps in itertools.permutations(range(5, 10))])) 75 | -------------------------------------------------------------------------------- /2019/08a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | list = list(next(fileinput.input()).strip()) 5 | 6 | size = 25 * 6 7 | min = float("inf") 8 | sum = None 9 | 10 | for i in range(0, len(list), size): 11 | layer = "".join(list[i : i + size]) # noqa: E203 12 | zeros = layer.count("0") 13 | if zeros < min: 14 | min = zeros 15 | sum = layer.count("1") * layer.count("2") 16 | 17 | print(sum) 18 | -------------------------------------------------------------------------------- /2019/08b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | list = list(next(fileinput.input()).strip()) 5 | 6 | w, h = 25, 6 7 | size = w * h 8 | image = ["2"] * size 9 | 10 | for i in range(0, len(list), size): 11 | for c, v in enumerate(list[i : i + size]): # noqa: E203 12 | if image[c] == "2": 13 | image[c] = v 14 | 15 | for i in range(0, len(image), w): 16 | print("".join(image[i : i + w]).replace("0", " ")) # noqa: E203 17 | -------------------------------------------------------------------------------- /2019/09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | mem = {} 5 | 6 | 7 | def get(c, i): 8 | if i < len(c): 9 | return c[i] 10 | elif i in mem: 11 | return mem[i] 12 | else: 13 | return 0 14 | 15 | 16 | def read(c, i, mode, base): 17 | i = get(c, i) 18 | if mode == 0: 19 | i = get(c, i) 20 | elif mode == 2: 21 | i = get(c, i + base) 22 | return i 23 | 24 | 25 | def write(c, i, v, mode, base): 26 | if i < len(c): 27 | i = c[i] 28 | elif i in mem: 29 | i = mem[i] 30 | else: 31 | i = 0 32 | 33 | if mode == 2: 34 | i += base 35 | 36 | if i < len(c): 37 | c[i] = v 38 | else: 39 | mem[i] = v 40 | 41 | 42 | def compute(c, inp=None, i=0): 43 | if inp is None: 44 | inp = [] 45 | 46 | base = 0 47 | 48 | while i < len(c): 49 | # print(f"{i:2}: {c[i]:5} {c} base: {base} {mem}") 50 | opcode = c[i] % 100 51 | modes = str(c[i]).zfill(5)[0:-2] 52 | 53 | if opcode in (1, 2, 7, 8): # add, multiply, less than, equals 54 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 55 | b = read(c, i + 2, int(modes[-2]), base) # thousands 56 | 57 | if opcode == 1: 58 | write(c, i + 3, a + b, int(modes[-3]), base) 59 | elif opcode == 2: 60 | write(c, i + 3, a * b, int(modes[-3]), base) 61 | elif opcode == 7: 62 | write(c, i + 3, 1 if a < b else 0, int(modes[-3]), base) 63 | elif opcode == 8: 64 | write(c, i + 3, 1 if a == b else 0, int(modes[-3]), base) 65 | 66 | i += 4 67 | elif opcode == 3: # input 68 | write(c, i + 1, inp.pop(0), int(modes[-1]), base) 69 | i += 2 70 | elif opcode == 4: # output 71 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 72 | output = a 73 | # print(output) 74 | i += 2 75 | elif opcode in (5, 6): # jump-if-true, jump-if-false 76 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 77 | 78 | if (opcode == 5 and a != 0) or (opcode == 6 and a == 0): 79 | i = read(c, i + 2, int(modes[-2]), base) # thousands 80 | else: 81 | i += 3 82 | elif opcode == 9: # base 83 | base += read(c, i + 1, int(modes[-1]), base) # hundreds 84 | i += 2 85 | elif opcode == 99: 86 | break 87 | 88 | return output 89 | 90 | 91 | code = [int(i) for i in next(fileinput.input()).strip().split(",")] 92 | print(compute(code, [1])) 93 | print(compute(code, [2])) 94 | -------------------------------------------------------------------------------- /2019/10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from cmath import polar 4 | 5 | a = {} 6 | 7 | for y, line in enumerate(fileinput.input()): 8 | for x, v in enumerate(line.strip()): 9 | if v == "#": 10 | a[complex(x, y)] = {} 11 | 12 | for i in a.keys(): 13 | for j in a.keys(): 14 | if i != j: 15 | r, phi = polar((j - i) / 1j) # rotate 90 degrees 16 | if phi not in a[i]: 17 | a[i][phi] = {} 18 | a[i][phi][r] = j 19 | 20 | x = max(a, key=(lambda k: len(a[k]))) 21 | print(len(a[x])) 22 | 23 | x = a[x] 24 | i = 200 25 | 26 | while True: 27 | for phi in sorted(x.keys()): 28 | r = sorted(x[phi].keys())[0] 29 | 30 | i -= 1 31 | if i <= 0: 32 | print(x[phi][r].real * 100 + x[phi][r].imag) 33 | break 34 | 35 | del x[phi][r] 36 | if len(x[phi]) == 0: 37 | del x[phi] 38 | 39 | if i <= 0 or len(x) == 0: 40 | break 41 | -------------------------------------------------------------------------------- /2019/11.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from collections import defaultdict 4 | 5 | mem = {} 6 | 7 | 8 | def get(c, i): 9 | if i < len(c): 10 | return c[i] 11 | elif i in mem: 12 | return mem[i] 13 | else: 14 | return 0 15 | 16 | 17 | def read(c, i, mode, base): 18 | i = get(c, i) 19 | if mode == 0: 20 | i = get(c, i) 21 | elif mode == 2: 22 | i = get(c, i + base) 23 | return i 24 | 25 | 26 | def write(c, i, v, mode, base): 27 | if i < len(c): 28 | i = c[i] 29 | elif i in mem: 30 | i = mem[i] 31 | else: 32 | i = 0 33 | 34 | if mode == 2: 35 | i += base 36 | 37 | if i < len(c): 38 | c[i] = v 39 | else: 40 | mem[i] = v 41 | 42 | 43 | def compute(c, white=False): 44 | i = 0 45 | base = 0 46 | 47 | pm = 0 # paint, move 48 | t = [+1j, -1j] # -90 left, +90 right, 49 | d = 0 + 1j 50 | p = 0 + 0j # position 51 | panels = defaultdict(int) 52 | 53 | if white: 54 | panels[p] = 1 55 | 56 | def input(): 57 | return panels[p] 58 | 59 | def output(a): 60 | nonlocal pm, d, p 61 | if pm == 0: # paint 62 | panels[p] = a 63 | 64 | else: # move 65 | d *= t[a] # rotate 66 | p += d 67 | 68 | pm = (pm + 1) % 2 69 | 70 | while i < len(c): 71 | opcode = c[i] % 100 72 | modes = str(c[i]).zfill(5)[0:-2] 73 | 74 | if opcode in (1, 2, 7, 8): # add, multiply, less than, equals 75 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 76 | b = read(c, i + 2, int(modes[-2]), base) # thousands 77 | 78 | if opcode == 1: 79 | write(c, i + 3, a + b, int(modes[-3]), base) 80 | elif opcode == 2: 81 | write(c, i + 3, a * b, int(modes[-3]), base) 82 | elif opcode == 7: 83 | write(c, i + 3, 1 if a < b else 0, int(modes[-3]), base) 84 | elif opcode == 8: 85 | write(c, i + 3, 1 if a == b else 0, int(modes[-3]), base) 86 | 87 | i += 4 88 | elif opcode == 3: # input 89 | write(c, i + 1, input(), int(modes[-1]), base) 90 | i += 2 91 | elif opcode == 4: # output 92 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 93 | output(a) 94 | i += 2 95 | elif opcode in (5, 6): # jump-if-true, jump-if-false 96 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 97 | 98 | if (opcode == 5 and a != 0) or (opcode == 6 and a == 0): 99 | i = read(c, i + 2, int(modes[-2]), base) # thousands 100 | else: 101 | i += 3 102 | elif opcode == 9: # base 103 | base += read(c, i + 1, int(modes[-1]), base) # hundreds 104 | i += 2 105 | elif opcode == 99: 106 | break 107 | 108 | return panels 109 | 110 | 111 | code = [int(i) for i in next(fileinput.input()).strip().split(",")] 112 | 113 | print(len(compute(code[:], False))) 114 | 115 | panels = compute(code[:], True) 116 | reals = [int(i.real) for i in panels.keys()] 117 | imags = [int(i.imag) for i in panels.keys()] 118 | 119 | for i in range(max(imags), min(imags) - 1, -1): 120 | for j in range(min(reals), max(reals) + 1): 121 | print("#" if panels[complex(j, i)] == 1 else " ", end="") 122 | print() 123 | -------------------------------------------------------------------------------- /2019/12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | import re 4 | from copy import deepcopy 5 | from math import gcd 6 | 7 | 8 | # reversed comparison 9 | def cmp(a, b): 10 | return (a < b) - (a > b) 11 | 12 | 13 | def simulate(p, v): 14 | # print(f"After {s} steps:") 15 | # gravity 16 | g = [] 17 | for i, ii in enumerate(p): 18 | x = [0] * len(ii) 19 | for j, jj in enumerate(p): 20 | if i != j: 21 | for k in range(len(ii)): 22 | x[k] += cmp(ii[k], jj[k]) 23 | g.append(x) 24 | 25 | for i in range(len(p)): 26 | for j in range(len(v[i])): 27 | # velocity 28 | v[i][j] += g[i][j] 29 | # position 30 | p[i][j] += v[i][j] 31 | # print(f"{i} pos {p[i]} vel {v[i]}") 32 | return p, v 33 | 34 | 35 | def cycle(p, v, p_copy, v_copy): 36 | # print(p, v, p_copy, v_copy) 37 | # return -1 38 | s = 0 39 | while True: 40 | p, v = simulate(p, v) 41 | s += 1 42 | 43 | same = True 44 | for i in range(len(p)): 45 | if p[i] != p_copy[i]: 46 | same = False 47 | break 48 | if v[i] != v_copy[i]: 49 | same = False 50 | break 51 | 52 | if same: 53 | return s 54 | 55 | 56 | p = [] # positions 57 | v = [] # velocities 58 | 59 | for line in fileinput.input(): 60 | x = [int(i) for i in re.findall(r"=(-?\d+)", line)] 61 | p.append(x) 62 | v.append([0] * len(x)) 63 | 64 | p_copy = deepcopy(p) 65 | v_copy = deepcopy(v) 66 | 67 | # A 68 | 69 | for s in range(1000): 70 | p, v = simulate(p, v) 71 | 72 | e = 0 73 | for i in range(len(p)): 74 | e += sum(abs(j) for j in p[i]) * sum(abs(j) for j in v[i]) 75 | print(e) 76 | 77 | # B 78 | 79 | n = [] 80 | for i in range(len(p_copy[0])): 81 | p = [[c[i]] for c in p_copy] 82 | v = [[c[i]] for c in v_copy] 83 | n.append(cycle(p, v, deepcopy(p), deepcopy(v))) 84 | 85 | lcm = n[0] 86 | for i in n[1:]: 87 | lcm = lcm * i // gcd(lcm, i) 88 | print(lcm) 89 | -------------------------------------------------------------------------------- /2019/13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | mem = {} 5 | 6 | 7 | def get(c, i): 8 | if i < len(c): 9 | return c[i] 10 | elif i in mem: 11 | return mem[i] 12 | else: 13 | return 0 14 | 15 | 16 | def read(c, i, mode, base): 17 | i = get(c, i) 18 | if mode == 0: 19 | i = get(c, i) 20 | elif mode == 2: 21 | i = get(c, i + base) 22 | return i 23 | 24 | 25 | def write(c, i, v, mode, base): 26 | if i < len(c): 27 | i = c[i] 28 | elif i in mem: 29 | i = mem[i] 30 | else: 31 | i = 0 32 | 33 | if mode == 2: 34 | i += base 35 | 36 | if i < len(c): 37 | c[i] = v 38 | else: 39 | mem[i] = v 40 | 41 | 42 | def compute(c, inp=None, outp=None, i=0): 43 | if inp is None: 44 | inp = [] 45 | 46 | if outp is None: 47 | outp = [] 48 | 49 | base = 0 50 | 51 | while i < len(c): 52 | # print(f"{i:2}: {c[i]:5} {c} base: {base} {mem}") 53 | opcode = c[i] % 100 54 | modes = str(c[i]).zfill(5)[0:-2] 55 | 56 | if opcode in (1, 2, 7, 8): # add, multiply, less than, equals 57 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 58 | b = read(c, i + 2, int(modes[-2]), base) # thousands 59 | 60 | if opcode == 1: 61 | write(c, i + 3, a + b, int(modes[-3]), base) 62 | elif opcode == 2: 63 | write(c, i + 3, a * b, int(modes[-3]), base) 64 | elif opcode == 7: 65 | write(c, i + 3, 1 if a < b else 0, int(modes[-3]), base) 66 | elif opcode == 8: 67 | write(c, i + 3, 1 if a == b else 0, int(modes[-3]), base) 68 | 69 | i += 4 70 | elif opcode == 3: # input 71 | write(c, i + 1, inp.pop(0), int(modes[-1]), base) 72 | i += 2 73 | elif opcode == 4: # output 74 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 75 | outp.append(a) 76 | # print(output) 77 | i += 2 78 | elif opcode in (5, 6): # jump-if-true, jump-if-false 79 | a = read(c, i + 1, int(modes[-1]), base) # hundreds 80 | 81 | if (opcode == 5 and a != 0) or (opcode == 6 and a == 0): 82 | i = read(c, i + 2, int(modes[-2]), base) # thousands 83 | else: 84 | i += 3 85 | elif opcode == 9: # base 86 | base += read(c, i + 1, int(modes[-1]), base) # hundreds 87 | i += 2 88 | elif opcode == 99: 89 | break 90 | 91 | return outp 92 | 93 | 94 | code = [int(i) for i in next(fileinput.input()).strip().split(",")] 95 | o = compute(code) 96 | 97 | g = {} 98 | i = 0 99 | 100 | while i < len(o): 101 | g[(o[i], o[i + 1])] = o[i + 2] 102 | i += 3 103 | 104 | print(len([i for i in g if g[i] == 2])) 105 | -------------------------------------------------------------------------------- /2020/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from math import prod 4 | from itertools import combinations 5 | 6 | # Slower, but works in case of "n" containing duplicates or single 1010 7 | 8 | 9 | def solve(length): 10 | for c in combinations(n, length): 11 | if sum(c) == 2020: 12 | return prod(c) 13 | 14 | 15 | n = list(map(int, fileinput.input())) 16 | print(solve(2)) 17 | print(solve(3)) 18 | 19 | # Faster alternatives, but could lead into false positives if "n" contains duplicates or single 1010 20 | 21 | 22 | def two(n): 23 | s = set(n) 24 | for x in s: 25 | if (y := 2020 - x) in s: 26 | return x * y 27 | 28 | 29 | def tree(n): 30 | s = set(n) 31 | for i, x in enumerate(n): 32 | yz = 2020 - x 33 | for y in n[i + 1 :]: 34 | if (z := yz - y) in s: 35 | return x * y * z 36 | 37 | 38 | print(two(n)) 39 | print(tree(n)) 40 | -------------------------------------------------------------------------------- /2020/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | import re 4 | 5 | # 1-3 a: abcde 6 | p = re.compile(r"^(\d+)-(\d+) (.): (.*)$") 7 | 8 | s1 = 0 9 | s2 = 0 10 | 11 | for line in fileinput.input(): 12 | (low, high, letter, password) = p.match(line.strip()).groups() 13 | low, high = int(low), int(high) 14 | 15 | if low <= password.count(letter) <= high: 16 | s1 += 1 17 | 18 | if (password[low - 1] == letter) ^ (password[high - 1] == letter): 19 | s2 += 1 20 | 21 | print(s1) 22 | print(s2) 23 | -------------------------------------------------------------------------------- /2020/03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from math import prod 4 | from itertools import count 5 | 6 | 7 | def trees(r_init, d_init, m): 8 | r, d, w, t = r_init, d_init, len(m[0]), 0 9 | 10 | while d < len(m): 11 | t += m[d][r % w] == "#" 12 | r += r_init 13 | d += d_init 14 | 15 | return t 16 | 17 | 18 | m = [l.strip() for l in fileinput.input()] 19 | print(trees(3, 1, m)) 20 | print(prod(trees(*init, m) for init in [(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)])) 21 | 22 | # Faster alternative 23 | 24 | 25 | def toboggan(m, right, down): 26 | return sum( 27 | # fmt: off 28 | m[row][col % len(m[row])] == "#" 29 | for row, col in zip( 30 | range(0, len(m), down), 31 | count(0, right) 32 | ) 33 | # fmt: on 34 | ) 35 | 36 | 37 | print(toboggan(m, 3, 1)) 38 | print(prod(toboggan(m, *init) for init in [(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)])) 39 | -------------------------------------------------------------------------------- /2020/04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import re 4 | 5 | # fmt:off 6 | fields = { 7 | "byr": lambda x: 1920 <= int(x) <= 2002, 8 | "iyr": lambda x: 2010 <= int(x) <= 2020, 9 | "eyr": lambda x: 2020 <= int(x) <= 2030, 10 | "hgt": lambda x: (x.endswith("cm") and 150 <= int(x[:-2]) <= 193) or (x.endswith("in") and 59 <= int(x[:-2]) <= 76), 11 | "hcl": lambda x: re.fullmatch(r"#[\da-f]{6}", x), 12 | "ecl": lambda x: x in ("amb", "blu", "brn", "gry", "grn", "hzl", "oth"), 13 | "pid": lambda x: re.fullmatch(r"\d{9}", x), 14 | } 15 | # fmt:on 16 | present = 0 17 | valid = 0 18 | 19 | for line in sys.stdin.read().split("\n\n"): 20 | passport = dict(l.split(":") for l in line.split()) 21 | 22 | if not passport.keys() >= fields.keys(): 23 | continue 24 | 25 | present += 1 26 | valid += all(data(passport[field]) for field, data in fields.items()) 27 | 28 | print(present) 29 | print(valid) 30 | -------------------------------------------------------------------------------- /2020/05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | t = str.maketrans("FBLR", "0101") 5 | s = set(int(l.translate(t), 2) for l in fileinput.input()) 6 | lo, hi = min(s), max(s) 7 | 8 | print(hi) 9 | print(next(i for i in range(lo + 1, hi) if i not in s)) 10 | -------------------------------------------------------------------------------- /2020/06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | s1 = s2 = 0 5 | 6 | for group in sys.stdin.read().split("\n\n"): 7 | s1 += len(set(group.replace("\n", ""))) 8 | s2 += len(set.intersection(*map(set, group.split()))) 9 | 10 | print(s1) 11 | print(s2) 12 | -------------------------------------------------------------------------------- /2020/07.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | import re 4 | from collections import defaultdict, deque 5 | from functools import cache 6 | 7 | bags_in = defaultdict(dict) # values are bags inside the bag 8 | bags_out = defaultdict(set) # values are bags outside of the bag 9 | 10 | for line in fileinput.input(): 11 | parent, children = line.split(" bags contain ") 12 | for count, child in re.findall(r"(\d+) (\w+ \w+) bags?[,.]", children): 13 | bags_in[parent][child] = int(count) 14 | bags_out[child].add(parent) 15 | 16 | 17 | @cache 18 | def inside(name): 19 | return sum(count + count * inside(bag) for bag, count in bags_in[name].items()) 20 | 21 | 22 | @cache 23 | def outside(name): 24 | s = bags_out[name].copy() 25 | for bag in bags_out[name]: 26 | s.update(outside(bag)) 27 | return s 28 | 29 | 30 | print(len(outside("shiny gold"))) 31 | print(inside("shiny gold")) 32 | 33 | # Nonrecursive alternatives, faster then non-cached recursive ones, but slower than cached ones 34 | 35 | 36 | def search_inside(name): 37 | colors, total = deque([(name, 1)]), -1 # compensate the total count for the "name" bag itself 38 | 39 | while colors: 40 | color, multiplier = colors.pop() 41 | total += multiplier 42 | for child, count in bags_in[color].items(): 43 | colors.appendleft((child, multiplier * count)) 44 | 45 | return total 46 | 47 | 48 | def search_outside(name): 49 | colors, parents = deque([name]), set() 50 | 51 | while colors: 52 | for parent in bags_out[colors.pop()]: 53 | if parent not in parents: 54 | parents.add(parent) 55 | colors.appendleft(parent) 56 | 57 | return len(parents) 58 | 59 | 60 | print(search_outside("shiny gold")) 61 | print(search_inside("shiny gold")) 62 | -------------------------------------------------------------------------------- /2020/08.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | 5 | def run(code, visited, accumulator=0, i=0): 6 | while i not in visited and i < len(code): 7 | visited[i] = accumulator 8 | operation, number = code[i] 9 | 10 | if operation == "acc": 11 | accumulator += number 12 | elif operation == "jmp": 13 | i += number - 1 14 | 15 | i += 1 16 | 17 | return accumulator, i 18 | 19 | 20 | code, visited = [], {} 21 | 22 | for line in fileinput.input(): 23 | operation, number = line.split() 24 | code.append((operation, int(number))) 25 | 26 | accumulator, _ = run(code, visited) 27 | print(accumulator) # 1st part 28 | 29 | # Non-brute force approach: 30 | # Get a copy of the initial visited instrunctions 31 | # because run() modifies the dict by accumulating 32 | # newly visited instructions to prevent further loops 33 | for j in set(visited.keys()): 34 | operation, number = code[j] 35 | # Skip instructions that would still continue in loop(s) 36 | # fmt: off 37 | if (operation == "nop" and (i := j + number) not in visited) or \ 38 | (operation == "jmp" and (i := j + 1) not in visited): 39 | # fmt: on 40 | # And continue just from the next instruction with restored state 41 | accumulator, i = run(code, visited, visited[j], i) 42 | if i >= len(code): 43 | print(accumulator) # 2nd part 44 | break 45 | -------------------------------------------------------------------------------- /2020/09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from collections import deque 4 | 5 | n = [int(x) for x in fileinput.input()] 6 | 7 | # Kudos to https://github.com/jakobsen/advent-of-code-2020/blob/master/09.py 8 | preamble = deque(n[:25]) 9 | invalid = None 10 | 11 | for i in n[25:]: 12 | seen = set() 13 | for j in preamble: 14 | if i - j in seen: 15 | preamble.popleft() 16 | preamble.append(i) 17 | break 18 | seen.add(j) 19 | else: 20 | invalid = i 21 | break 22 | 23 | print(invalid) 24 | 25 | start = stop = total = 0 26 | 27 | while total != invalid: 28 | while total < invalid: 29 | total += n[stop] 30 | stop += 1 31 | while total > invalid: 32 | total -= n[start] 33 | start += 1 34 | 35 | print(min(n[start:stop]) + max(n[start:stop])) 36 | -------------------------------------------------------------------------------- /2020/10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from collections import defaultdict 4 | 5 | addapters = [0] + sorted(map(int, fileinput.input())) 6 | addapters.append(addapters[-1] + 3) 7 | 8 | diffs = defaultdict(int) 9 | counts = defaultdict(int, {0: 1}) 10 | 11 | for a, b in zip(addapters[1:], addapters): 12 | diffs[a - b] += 1 13 | # number of ways to reach i'th adapter from previous three possible ones 14 | counts[a] = counts[a - 3] + counts[a - 2] + counts[a - 1] 15 | 16 | print(diffs[1] * diffs[3]) 17 | print(counts[addapters[-1]]) 18 | -------------------------------------------------------------------------------- /2020/11.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import defaultdict 3 | 4 | 5 | def solve(seats, neighbors, limit): 6 | while True: 7 | new_seats = {} 8 | for seat, occupied in seats.items(): 9 | count = (seats[neighbor] for neighbor in neighbors[seat]) 10 | new_seats[seat] = sum(count) < limit if occupied else not any(count) 11 | 12 | if seats == new_seats: 13 | return sum(new_seats.values()) 14 | 15 | seats = new_seats 16 | 17 | 18 | lines = open(0).read().splitlines() 19 | size = len(lines) # grid width and height are the same 20 | # fmt: off 21 | seats = { 22 | row + col*1j: False 23 | for row, line in enumerate(lines) 24 | for col, char in enumerate(line) 25 | if char == "L" 26 | } 27 | 28 | directions = { 29 | direction 30 | for row in (-1, 0, 1) 31 | for col in (-1, 0, 1) 32 | if (direction := row + col*1j) # skip 0+0*j 33 | } 34 | 35 | # Precompute all possible (in)direct neighbors of each seat 36 | 37 | neighbors_direct = { 38 | seat: [ 39 | neighbor 40 | for direction in directions 41 | if (neighbor := seat + direction) in seats 42 | ] 43 | for seat in seats 44 | } 45 | # fmt: on 46 | neighbors_adjacent = defaultdict(list) 47 | for seat in seats: 48 | for direction in directions: 49 | neighbor = seat + direction 50 | while 0 <= neighbor.real < size and 0 <= neighbor.imag < size: 51 | if neighbor in seats: 52 | neighbors_adjacent[seat].append(neighbor) 53 | break 54 | neighbor += direction 55 | 56 | print(solve(seats, neighbors_direct, 4)) 57 | print(solve(seats, neighbors_adjacent, 5)) 58 | -------------------------------------------------------------------------------- /2020/12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | 4 | 5 | def solve(instructions, waypoint=1 + 0j): 6 | actions = {"N": 1j, "S": -1j, "E": 1, "W": -1} 7 | position = 0 + 0j 8 | waypoint_mode = waypoint != 1 + 0j 9 | 10 | for action, value in instructions: 11 | if action == "L": 12 | waypoint *= pow(1j, value // 90) 13 | elif action == "R": 14 | waypoint *= pow(-1j, value // 90) 15 | elif action == "F": 16 | position += waypoint * value 17 | elif waypoint_mode: 18 | waypoint += actions[action] * value 19 | else: 20 | position += actions[action] * value 21 | 22 | return int(abs(position.real) + abs(position.imag)) 23 | 24 | 25 | instructions = [(line[0], int(line[1:])) for line in fileinput.input()] 26 | print(solve(instructions)) 27 | print(solve(instructions, 10 + 1j)) 28 | -------------------------------------------------------------------------------- /2020/13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from math import prod 3 | 4 | timestamp, busses = open(0).read().splitlines() 5 | timestamp, busses = int(timestamp), { 6 | # fmt: off 7 | i: int(bus) 8 | for i, bus in enumerate(busses.split(",")) 9 | if bus != "x" 10 | # fmt: on 11 | } 12 | 13 | print(prod(min((-timestamp % bus, bus) for bus in busses.values()))) 14 | 15 | # Slower, but probably easier to understand 16 | 17 | t, step = 0, 1 18 | 19 | for delta, bus in busses.items(): 20 | while (t + delta) % bus: 21 | t += step 22 | step *= bus # All busses are co-primes, so their LCM is their multiplication 23 | 24 | print(t) 25 | 26 | # Faster alternative, but requires understanding of the Chinese remainder theorem 27 | 28 | t, P = 0, prod(busses.values()) 29 | 30 | for a, p in busses.items(): 31 | n = P // p 32 | inv = pow(n, -1, p) 33 | t = (t - a * n * inv) % P 34 | 35 | print(t) 36 | -------------------------------------------------------------------------------- /2020/14.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | from itertools import chain, combinations 4 | 5 | 6 | def powerset(s): 7 | "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" 8 | return chain.from_iterable( 9 | # fmt: off 10 | combinations(s, r) 11 | for r in range(len(s) + 1) 12 | # fmt: on 13 | ) 14 | 15 | 16 | table = str.maketrans("1X", "01") 17 | m1 = {} 18 | m2 = {} 19 | 20 | for line in fileinput.input(): 21 | key, value = line.strip().split(" = ") 22 | if key == "mask": 23 | mask = value 24 | mask_clear = int(mask.translate(table), 2) 25 | mask_set = int(mask.replace("X", "0"), 2) 26 | offsets = [ 27 | # fmt: off 28 | sum(x) 29 | for x in powerset([ 30 | 1 << (35 - i) # 2 ** (35 - 1) 31 | for i, bit in enumerate(mask) 32 | if bit == "X" 33 | ]) 34 | # fmt: on 35 | ] 36 | else: 37 | address, value = int(key[4:-1]), int(value) 38 | 39 | m1[address] = value & mask_clear | mask_set 40 | 41 | address = address & ~mask_clear | mask_set 42 | for offset in offsets: 43 | m2[address | offset] = value 44 | 45 | print(sum(m1.values())) 46 | print(sum(m2.values())) 47 | -------------------------------------------------------------------------------- /2020/15.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | import sys 3 | from array import array 4 | 5 | numbers = [int(i) for i in next(sys.stdin).split(",")] 6 | last = numbers[-1] 7 | size = 30_000_000 8 | # age = {number: i+1 for i, number in enumerate(numbers)} # slow dict alternative 9 | # Despite more memory consumption the preallocated list approach is faster than dict 10 | age = [0] * size 11 | for i, number in enumerate(numbers): 12 | age[number] = i + 1 13 | 14 | for i in range(len(numbers), size): 15 | if i == 2020: 16 | print(last) 17 | # age[last], last = i, i - age.get(last, i) # slow dict alternative 18 | previous = age[last] 19 | age[last], last = i, i - previous if previous else 0 20 | 21 | print(last) 22 | -------------------------------------------------------------------------------- /2020/16.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | 4 | raw_rules, your_ticket, nearby_tickets = open(0).read().split("\n\n") 5 | regexp = re.compile(r"([^:]+): (\d+)-(\d+) or (\d+)-(\d+)") 6 | rules = [] 7 | 8 | for line in raw_rules.splitlines(): 9 | field, lo1, hi1, lo2, hi2 = regexp.fullmatch(line).groups() 10 | rules.append((field, int(lo1), int(hi1), int(lo2), int(hi2))) 11 | 12 | error_rate = 0 13 | rules_count = len(rules) 14 | # start with all rules (indexes) being valid for each column 15 | cols = [set(range(rules_count)) for _ in range(rules_count)] 16 | 17 | for ticket in nearby_tickets.splitlines()[1:]: 18 | valid_ticket = True 19 | ticket_rules = [] 20 | 21 | for number in map(int, ticket.split(",")): 22 | matching_rules = set( 23 | # fmt: off 24 | i 25 | for i, (_, lo1, hi1, lo2, hi2) in enumerate(rules) 26 | if lo1 <= number <= hi1 or lo2 <= number <= hi2 27 | # fmt: on 28 | ) 29 | if not matching_rules: 30 | error_rate += number 31 | valid_ticket = False 32 | elif valid_ticket: 33 | ticket_rules.append(matching_rules) 34 | 35 | if valid_ticket: 36 | for col, matching_rules in zip(cols, ticket_rules): 37 | col &= matching_rules # col is a reference, not a copy 38 | 39 | print(error_rate) 40 | 41 | total = 1 42 | singles = set() 43 | your_ticket = list(map(int, your_ticket.splitlines()[-1].split(","))) 44 | 45 | while len(singles) != rules_count: 46 | for i, col in enumerate(cols): 47 | if len(col) > 1: 48 | col -= singles 49 | elif len(col) == 1: 50 | singles |= col 51 | if rules[col.pop()][0].startswith("departure"): 52 | total *= your_ticket[i] 53 | 54 | print(total) 55 | -------------------------------------------------------------------------------- /2020/17.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import Counter 3 | from itertools import product 4 | 5 | 6 | def solve(lines, dimensions): 7 | zeros = [0] * (dimensions - 2) 8 | coordinates = {*product((-1, 0, 1), repeat=dimensions)} - {(0, 0, *zeros)} 9 | # fmt: off 10 | cubes = { 11 | (row, col, *zeros) 12 | for row, line in enumerate(lines) 13 | for col, char in enumerate(line) 14 | if char == "#" 15 | } 16 | # fmt: on 17 | 18 | for _ in range(6): 19 | # fmt: off 20 | # Kudos to https://github.com/dionyziz/advent-of-code/blob/master/2020/17/17.py 21 | neighbors = Counter( 22 | tuple(sum(x) for x in zip(cube, coordinate)) 23 | for cube in cubes 24 | for coordinate in coordinates 25 | ) # possibility of a future neighbor 26 | 27 | cubes = { 28 | neighbor 29 | for neighbor, count in neighbors.items() 30 | if count == 3 or (count == 2 and neighbor in cubes) 31 | } 32 | # fmt: on 33 | 34 | return len(cubes) 35 | 36 | 37 | lines = open(0).read().splitlines() 38 | print(solve(lines, 3)) 39 | print(solve(lines, 4)) 40 | -------------------------------------------------------------------------------- /2020/18.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | import sys 4 | 5 | 6 | class I(int): 7 | def __add__(a, b): 8 | return I(a.real + b.real) 9 | 10 | def __sub__(a, b): 11 | return I(a.real * b.real) 12 | 13 | __mul__ = __add__ 14 | 15 | 16 | lines = re.sub(r"(\d+)", r"I(\1)", sys.stdin.read()).replace("*", "-").splitlines() 17 | print(sum(eval(line) for line in lines)) 18 | print(sum(eval(line.replace("+", "*")) for line in lines)) 19 | -------------------------------------------------------------------------------- /2020/19.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import regex 4 | 5 | 6 | # Kudos to https://github.com/taddeus/advent-of-code/blob/master/2020/19_regex.py 7 | def solve(rules, messages): 8 | def expand(value): 9 | if not value.isdigit(): 10 | return value 11 | return "(?:" + "".join(map(expand, rules[value].split())) + ")" 12 | 13 | r = regex.compile(expand("0")) 14 | return sum(r.fullmatch(m) is not None for m in messages) 15 | 16 | 17 | raw_rules, messages = sys.stdin.read().split("\n\n") 18 | messages = messages.splitlines() 19 | rules = dict( 20 | # fmt: off 21 | raw_rule.replace('"', "").split(": ", 1) 22 | for raw_rule in raw_rules.splitlines() 23 | # fmt: on 24 | ) 25 | 26 | print(solve(rules, messages)) 27 | rules["8"] = "42 +" # repeat pattern 28 | rules["11"] = "(?P 42 (?&R)? 31 )" # recursive pattern 29 | print(solve(rules, messages)) 30 | -------------------------------------------------------------------------------- /2020/20.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | import sys 4 | from math import prod 5 | from operator import itemgetter 6 | 7 | 8 | def add(tiles, borders, name, lines, include_flipped=True): 9 | top, bottom = lines[0], lines[-1] 10 | left, right = "".join(map(itemgetter(0), lines)), "".join(map(itemgetter(-1), lines)) 11 | 12 | tile_borders = {} 13 | 14 | # fmt: off 15 | # Start with flipped sides first. If non-flipped side mirrors its flipped side, 16 | # the later will overwrite the previous. 17 | if include_flipped: 18 | tile_borders |= { 19 | top[::-1]: (TOP, LR_FLIP), # flipped 20 | bottom[::-1]: (BOTTOM, LR_FLIP), # flipped 21 | left[::-1]: (LEFT, TB_FLIP), # flipped 22 | right[::-1]: (RIGHT, TB_FLIP), # flipped 23 | } 24 | 25 | tile_borders |= { 26 | top: (TOP, NO_FLIP), 27 | bottom: (BOTTOM, NO_FLIP), 28 | left: (LEFT, NO_FLIP), 29 | right: (RIGHT, NO_FLIP), 30 | } 31 | # fmt: on 32 | 33 | tiles[name], borders[name] = lines, tile_borders 34 | 35 | 36 | def transform(tiles, borders, name, matching_border, matching_side): 37 | lines, (orientation, flip) = tiles[name], borders[name][matching_border] 38 | 39 | if flip == TB_FLIP: 40 | lines = tb_flip(lines) 41 | elif flip == LR_FLIP: 42 | lines = lr_flip(lines) 43 | 44 | if destination := orientation + matching_side: # != +0+0j 45 | if destination in [2 * TOP, 2 * BOTTOM]: 46 | lines = tb_flip(lines) 47 | elif destination in [2 * LEFT, 2 * RIGHT]: 48 | lines = lr_flip(lines) 49 | elif orientation.real: 50 | lines = l_rotate(lines) 51 | if orientation.real == matching_side.imag: 52 | lines = tb_flip(lines) 53 | else: # orientation.imag 54 | lines = r_rotate(lines) 55 | if orientation.imag == matching_side.real: 56 | lines = lr_flip(lines) 57 | 58 | add(tiles, borders, name, lines, False) 59 | 60 | if matching_border in borders[name]: 61 | del borders[name][matching_border] 62 | 63 | 64 | NO_FLIP, LR_FLIP, TB_FLIP = range(3) 65 | TOP, BOTTOM, LEFT, RIGHT = 1j, -1j, -1, 1 66 | 67 | tb_flip = lambda lines: list(reversed(lines)) 68 | lr_flip = lambda lines: [line[::-1] for line in lines] 69 | l_rotate = lambda lines: list(map("".join, reversed(list(zip(*lines))))) 70 | r_rotate = lambda lines: list(map("".join, zip(*reversed(lines)))) 71 | hashes = lambda matrix: sum(row.count("#") for row in matrix) 72 | 73 | tiles, borders = {}, {} 74 | 75 | for tile in sys.stdin.read().strip().split("\n\n"): 76 | name, *lines = tile.splitlines() 77 | add(tiles, borders, int(name[-5:-1]), lines) 78 | 79 | stack = set(borders.keys()) 80 | center = stack.pop() # Pick a random tile as a center 81 | image = {+0 + 0j: center} 82 | tile_size = len(tiles[center]) 83 | add(tiles, borders, center, tiles[center], False) # And assume it does not need flipping 84 | 85 | min_row = max_row = min_col = max_col = 0 86 | 87 | while stack: 88 | candidate = stack.pop() 89 | 90 | for position, source in image.items(): 91 | if match := borders[source].keys() & borders[candidate].keys(): 92 | break 93 | 94 | if not match: 95 | stack.add(candidate) 96 | continue 97 | 98 | matching_border = match.pop() # Unpack the first (and only) matching border 99 | matching_side = borders[source][matching_border][0] 100 | 101 | position += matching_side 102 | image[position] = candidate 103 | 104 | min_row, max_row = min(min_row, int(position.imag)), max(max_row, int(position.imag)) 105 | min_col, max_col = min(min_col, int(position.real)), max(max_col, int(position.real)) 106 | 107 | transform(tiles, borders, candidate, matching_border, matching_side) 108 | del borders[source][matching_border] 109 | 110 | # fmt: off 111 | print(prod( 112 | image[c + r * 1j] 113 | for r in (min_row, max_row) 114 | for c in (min_col, max_col) 115 | )) 116 | 117 | grid = [ 118 | "".join( 119 | tiles[image[col + row * 1j]][i][1:-1] 120 | for col in range(min_col, max_col + 1) 121 | ) 122 | for row in range(max_row, min_row-1, -1) 123 | for i in range(1, tile_size-1) 124 | ] 125 | # fmt: on 126 | monster = ( 127 | " # ", 128 | "# ## ## ###", 129 | " # # # # # # ", 130 | ) 131 | padding = len(grid[0]) - len(monster[0]) + len("\n") 132 | # Regex search is elegant and fast, however because of overlapping strings 133 | # lookahead approach needs to be used 134 | r = re.compile("(?=(" + f".{{{padding}}}".join(monster).replace(" ", ".") + "))", re.DOTALL) 135 | 136 | monsters = 0 137 | 138 | for _ in range(2): # no flip, flip 139 | for _ in range(4): # 4 rotations 140 | monsters = max(monsters, len(r.findall("\n".join(grid)))) 141 | grid = r_rotate(grid) # or l_rotate 142 | grid = lr_flip(grid) # or tb_flip 143 | 144 | print(hashes(grid) - hashes(monster) * monsters) 145 | -------------------------------------------------------------------------------- /2020/21.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import Counter 3 | 4 | ing_counts = Counter() 5 | alg_ings = {} # possible allergen -> ingredients candidates 6 | 7 | for line in open(0).read().splitlines(): 8 | ings, algs = line.split(" (contains ") 9 | ings, algs = ings.split(), algs[:-1].split(", ") 10 | 11 | ing_counts.update(ings) 12 | 13 | for alg in algs: 14 | if alg in alg_ings: 15 | alg_ings[alg] &= set(ings) 16 | else: 17 | alg_ings[alg] = set(ings) 18 | 19 | singles = set() 20 | while len(singles) != len(alg_ings): 21 | for alg, ings in alg_ings.items(): 22 | if len(ings) > 1: 23 | ings -= singles 24 | else: # len(ings) == 1 25 | singles |= ings 26 | # fmt: off 27 | print(sum( 28 | count 29 | for ing, count in ing_counts.items() 30 | if ing not in set.union(*alg_ings.values()) 31 | )) 32 | 33 | print(",".join( 34 | alg_ings[alg].pop() 35 | for alg in sorted(alg_ings) 36 | )) 37 | # fmt: on 38 | -------------------------------------------------------------------------------- /2020/22.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from math import prod 4 | 5 | 6 | # Kudos to https://github.com/hltk/adventofcode/blob/main/2020/22.py 7 | def game(player1, player2, recursive): 8 | seen = set() 9 | 10 | while player1 and player2: 11 | if (state := (tuple(player1), tuple(player2))) in seen: 12 | return True, player1 13 | seen.add(state) 14 | 15 | (card1, *player1), (card2, *player2) = player1, player2 16 | 17 | if recursive and len(player1) >= card1 and len(player2) >= card2: 18 | cards1, cards2 = player1[:card1], player2[:card2] 19 | # fmt: off 20 | if (cards1_max := max(cards1)) > max(cards2) and \ 21 | (cards1_max > (len(cards1) + len(cards2) - 2)): 22 | player1win = True 23 | else: 24 | player1win = game(cards1, cards2, recursive)[0] 25 | # fmt: on 26 | else: 27 | player1win = card1 > card2 28 | 29 | if player1win: 30 | player1.extend((card1, card2)) 31 | else: 32 | player2.extend((card2, card1)) 33 | 34 | return (True, player1) if player1 else (False, player2) 35 | 36 | 37 | players = [ 38 | # fmt: off 39 | list(map(int, player.splitlines()[1:])) 40 | for player in sys.stdin.read().split("\n\n") 41 | # fmt: on 42 | ] 43 | 44 | for recursive in False, True: 45 | player = game(*players, recursive)[1] 46 | print(sum(map(prod, enumerate(reversed(player), 1)))) 47 | -------------------------------------------------------------------------------- /2020/23.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # Kudos to https://github.com/taddeus/advent-of-code/blob/master/2020/23_cups.py 3 | from array import array 4 | 5 | 6 | def move(cups, moves, pad): 7 | nex = array("I", range(1, pad + 2)) 8 | 9 | for label, next_label in zip(cups, cups[1:] + cups[:1]): 10 | nex[label] = next_label 11 | 12 | nex[0] = cups[0] # head, points to the current cup 13 | 14 | if pad > len(cups): 15 | nex[-1] = nex[0] 16 | nex[cups[-1]] = max(cups) + 1 17 | 18 | for _ in range(moves): 19 | rem = nex[nex[0]] 20 | nex[nex[0]] = nex[nex[nex[rem]]] 21 | allrem = rem, nex[rem], nex[nex[rem]] 22 | 23 | dest = nex[0] - 1 if nex[0] > 1 else pad 24 | while dest in allrem: 25 | dest = pad if dest == 1 else dest - 1 26 | 27 | nex[nex[nex[rem]]] = nex[dest] 28 | nex[dest] = rem 29 | 30 | nex[0] = nex[nex[0]] 31 | 32 | cup = nex[1] 33 | while cup != 1: 34 | yield cup 35 | cup = nex[cup] 36 | 37 | 38 | cups = list(map(int, input())) 39 | print("".join(map(str, move(cups, 100, len(cups))))) 40 | m = move(cups, 10_000_000, 1_000_000) 41 | print(next(m) * next(m)) 42 | -------------------------------------------------------------------------------- /2020/24.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | import fileinput 4 | from collections import Counter 5 | 6 | coordinates = { 7 | # fmt: off 8 | "ne": 0+1j, "nw": -1+1j, 9 | "e": 1+0j, "w": -1+0j, 10 | "se": 1-1j, "sw": 0-1j, 11 | # fmt: on 12 | } 13 | r = re.compile("|".join(coordinates)) 14 | tiles = set() # black tiles only 15 | 16 | for line in fileinput.input(): 17 | tile = sum(map(coordinates.__getitem__, r.findall(line))) 18 | tiles ^= {tile} 19 | 20 | print(len(tiles)) 21 | 22 | for _ in range(100): 23 | # fmt: off 24 | neighbors = Counter( 25 | tile + coordinate 26 | for tile in tiles 27 | for coordinate in coordinates.values() 28 | ) # possibility of a future neighbor 29 | 30 | tiles = { 31 | neighbor 32 | for neighbor, count in neighbors.items() 33 | if count == 2 or (count == 1 and neighbor in tiles) 34 | } 35 | # fmt: on 36 | 37 | print(len(tiles)) 38 | -------------------------------------------------------------------------------- /2020/25.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | card, door = list(map(int, open(0).read().splitlines())) 3 | subject, modulus, loop = 7, 20201227, 0 4 | 5 | # Baby-Step Giant-Step Algorithm 6 | n = int(card**0.5) 7 | babies = {pow(subject, j, modulus): j for j in range(n + 1)} 8 | # Fermat’s Little Theorem 9 | fermat = pow(subject, n * (modulus - 2), modulus) 10 | 11 | while card not in babies.keys(): 12 | loop += 1 13 | card = (card * fermat) % modulus 14 | 15 | print(pow(door, loop * n + babies[card], modulus)) 16 | -------------------------------------------------------------------------------- /2021/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | n = list(map(int, open(0).read().splitlines())) 3 | for i in 1, 3: 4 | print(sum(a < b for a, b in zip(n, n[i:]))) 5 | -------------------------------------------------------------------------------- /2021/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | aim = h = d = 0 3 | 4 | for cmd, x in map(str.split, open(0).read().splitlines()): 5 | x = int(x) 6 | # fmt: off 7 | if cmd == "down": aim += x 8 | elif cmd == "up": aim -= x 9 | else: h += x; d += aim * x # cmd == "forward" 10 | # fmt: on 11 | print(h * aim) 12 | print(h * d) 13 | -------------------------------------------------------------------------------- /2021/03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | numbers = open(0).read().splitlines() 3 | # fmt:off 4 | gamma = "".join( 5 | "10" if bits.count("1") > len(bits) / 2 else "01" 6 | for bits in zip(*numbers) 7 | ) 8 | # fmt:on 9 | print(int(gamma[::2], 2) * int(gamma[1::2], 2)) 10 | 11 | 12 | def rating(data, cmp): 13 | for i in range(len(data[0])): 14 | _01 = {"0": [], "1": []} 15 | 16 | for number in data: 17 | _01[number[i]].append(number) 18 | # fmt:off 19 | if len(data := _01[ 20 | "1" if cmp(len(_01["1"]), len(_01["0"])) else "0" 21 | ]) == 1: 22 | return int(data[0], 2) 23 | # fmt:on 24 | 25 | 26 | print(rating(numbers[:], int.__ge__) * rating(numbers[:], int.__lt__)) 27 | -------------------------------------------------------------------------------- /2021/04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from itertools import chain 3 | 4 | numbers, *data = open(0).read().strip().split("\n\n") 5 | 6 | boards = [] 7 | for board in data: 8 | # fmt:off 9 | rows = [ 10 | [int(x) for x in row.split()] 11 | for row in board.split("\n") 12 | ] 13 | # fmt:on 14 | boards.append([set(line) for line in chain(rows, zip(*rows))]) 15 | 16 | drawn, remaining = set(), set(range(len(boards))) 17 | 18 | for number in map(int, numbers.split(",")): 19 | drawn.add(number) 20 | 21 | for i in remaining.copy(): 22 | if any(line <= drawn for line in boards[i]): 23 | remaining.remove(i) 24 | 25 | if len(remaining) == len(boards) - 1 or not remaining: 26 | print(number * sum(set.union(*boards[i]) - drawn)) 27 | -------------------------------------------------------------------------------- /2021/05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import defaultdict 3 | import re 4 | 5 | c, r, v = defaultdict(int), re.compile(r"\d+"), [] 6 | 7 | for l in open(0).read().splitlines(): 8 | x1, y1, x2, y2 = map(int, r.findall(l)) 9 | v.append((complex(x1, y1), complex(x2, y2))) 10 | 11 | for diagonal in (False, True): 12 | for a, b in v: 13 | if diagonal ^ (a.real == b.real or a.imag == b.imag): 14 | i = b - a 15 | i /= max(abs(i.real), abs(i.imag)) 16 | while a != b + i: 17 | c[a] += 1 18 | a += i 19 | 20 | print(sum(v > 1 for v in c.values())) 21 | -------------------------------------------------------------------------------- /2021/06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import deque 3 | 4 | d = deque([0] * 9) 5 | 6 | for i in map(int, input().split(",")): 7 | d[i] += 1 8 | 9 | for part in (80, 256 - 80): 10 | for _ in range(part): 11 | d.rotate(-1) 12 | d[6] += d[-1] 13 | print(sum(d)) 14 | -------------------------------------------------------------------------------- /2021/07.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | c = sorted(map(int, input().split(","))) 3 | median, mean = c[len(c) // 2], sum(c) // len(c) 4 | # fmt:off 5 | print(sum( 6 | abs(median - i) 7 | for i in c 8 | )) 9 | print(sum( 10 | (y := abs(mean - i)) * (y + 1) // 2 11 | for i in c 12 | )) 13 | # fmt:on 14 | -------------------------------------------------------------------------------- /2021/08.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | part1 = part2 = 0 3 | 4 | for line in open(0).read().splitlines(): 5 | signals, output = line.split(" | ") 6 | 7 | # fmt:off 8 | d = { 9 | l: set(s) 10 | for s in signals.split() 11 | if (l := len(s)) in (2, 4) 12 | } 13 | 14 | n = "" 15 | for o in output.split(): 16 | l = len(o) 17 | if l == 2: n += "1"; part1 += 1 18 | elif l == 4: n += "4"; part1 += 1 19 | elif l == 3: n += "7"; part1 += 1 20 | elif l == 7: n += "8"; part1 += 1 21 | elif l == 5: 22 | s = set(o) 23 | if len(s & d[2]) == 2: n += "3" 24 | elif len(s & d[4]) == 2: n += "2" 25 | else: n += "5" 26 | else: # l == 6 27 | s = set(o) 28 | if len(s & d[2]) == 1: n += "6" 29 | elif len(s & d[4]) == 4: n += "9" 30 | else: n += "0" 31 | # fmt:on 32 | part2 += int(n) 33 | 34 | print(part1) 35 | print(part2) 36 | -------------------------------------------------------------------------------- /2021/09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from math import prod 3 | 4 | m = [list(map(int, line)) for line in open(0).read().splitlines()] 5 | h, w, part1, part2 = len(m), len(m[0]), 0, [] 6 | 7 | for r in range(h): 8 | for c in range(w): 9 | # fmt:off 10 | if any( 11 | m[r][c] >= m[x][y] 12 | for i, j in ((-1, 0), (1, 0), (0, -1), (0, 1)) 13 | if 0 <= (x := r + i) < h and 0 <= (y := c + j) < w 14 | ): 15 | continue 16 | # fmt:on 17 | part1 += m[r][c] + 1 18 | 19 | visited, visiting = set(), set([(r, c)]) 20 | 21 | while visiting: 22 | a, b = visiting.pop() 23 | visited.add((a, b)) 24 | 25 | for i, j in (-1, 0), (1, 0), (0, -1), (0, 1): 26 | # fmt:off 27 | if 0 <= (x := a + i) < h and 0 <= (y := b + j) < w \ 28 | and m[x][y] < 9 \ 29 | and (x, y) not in visited: 30 | visiting.add((x, y)) 31 | # fmt:on 32 | 33 | part2.append(len(visited)) 34 | 35 | print(part1) 36 | print(prod(sorted(part2)[-3:])) 37 | -------------------------------------------------------------------------------- /2021/10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import deque 3 | 4 | pairs = {"(": ")", "[": "]", "{": "}", "<": ">"} 5 | points = {")": 3, "]": 57, "}": 1197, ">": 25137} 6 | part1, part2 = 0, [] 7 | 8 | for line in open(0).read().splitlines(): 9 | stack = deque() 10 | for c in line: 11 | if c in "([{<": 12 | stack.appendleft(pairs[c]) 13 | elif c != stack.popleft(): 14 | part1 += points[c] 15 | break 16 | else: 17 | score = 0 18 | for c in stack: 19 | score = score * 5 + ")]}>".index(c) + 1 20 | part2.append(score) 21 | 22 | print(part1) 23 | print(sorted(part2)[len(part2) // 2]) 24 | -------------------------------------------------------------------------------- /2021/11.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # fmt:off 3 | octopuses = { 4 | complex(row, col): int(number) 5 | for row, line in enumerate(open(0).read().splitlines()) 6 | for col, number in enumerate(line) 7 | } 8 | # fmt:on 9 | step, part1, part2 = 0, 0, None 10 | 11 | while step := step + 1: 12 | flashing, flashed = set(), set() 13 | 14 | for o in octopuses.keys(): 15 | octopuses[o] += 1 16 | if octopuses[o] > 9: 17 | flashing.add(o) 18 | 19 | while flashing: 20 | o = flashing.pop() 21 | octopuses[o] = 0 22 | flashed.add(o) 23 | 24 | for i in ( 25 | # fmt:off 26 | -1 + 1j, -1j, +1 + 1j, 27 | -1, +1, 28 | -1 - 1j, +1j, +1 - 1j 29 | # fmt:on 30 | ): 31 | if (x := o + i) in octopuses and x not in flashed: 32 | octopuses[x] += 1 33 | if octopuses[x] > 9: 34 | flashing.add(x) 35 | 36 | if part2 is None and len(flashed) == len(octopuses): 37 | part2 = step 38 | 39 | if step <= 100: 40 | part1 += len(flashed) 41 | elif part2: 42 | break 43 | 44 | print(part1) 45 | print(part2) 46 | -------------------------------------------------------------------------------- /2021/12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import defaultdict, deque 3 | from functools import cache 4 | 5 | caves = defaultdict(list) 6 | 7 | for line in open(0).read().splitlines(): 8 | a, b = line.split("-") 9 | caves[a].append(b) 10 | caves[b].append(a) 11 | 12 | 13 | # Recursive version with result caching 14 | @cache 15 | def dfs(parent, lowers, duplicate): 16 | if parent == "end": 17 | return 1 18 | elif parent.islower(): 19 | if parent in lowers: 20 | if duplicate: 21 | return 0 22 | duplicate = True 23 | lowers |= {parent} 24 | 25 | return sum( 26 | dfs(child, frozenset(lowers), duplicate) for child in caves[parent] if child != "start" 27 | ) 28 | 29 | 30 | for duplicate in True, False: 31 | print(dfs("start", frozenset(), duplicate)) 32 | 33 | # Slightly slower, non recursive version with result caching 34 | for duplicate in True, False: 35 | count, search, cache = ( 36 | 0, 37 | deque((child, frozenset(), duplicate, None) for child in caves["start"]), 38 | {}, 39 | ) 40 | 41 | while search: 42 | parent, lowers, duplicate, start = search.popleft() 43 | 44 | # Caching 45 | if start is not None: 46 | cache[parent, lowers, duplicate] = count - start 47 | continue 48 | elif (parent, lowers, duplicate) in cache: 49 | count += cache[parent, lowers, duplicate] 50 | continue 51 | else: 52 | search.extendleft([(parent, lowers, duplicate, count)]) 53 | 54 | if parent == "end": 55 | count += 1 56 | continue 57 | elif parent.islower(): 58 | if parent in lowers: 59 | if duplicate: 60 | continue 61 | duplicate = True 62 | lowers |= {parent} 63 | 64 | search.extendleft( 65 | (child, frozenset(lowers), duplicate, None) 66 | for child in caves[parent] 67 | if child != "start" 68 | ) 69 | 70 | print(count) 71 | -------------------------------------------------------------------------------- /2021/13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | positions, folds = open(0).read().split("\n\n") 3 | image, ab, first = set(), [0, 0], True 4 | 5 | for position in positions.splitlines(): 6 | image.add(tuple(map(int, position.split(",")))) 7 | 8 | for fold in folds.splitlines(): 9 | i, j = int(fold[11] == "y"), int(fold[13:]) 10 | ab[i] = j 11 | 12 | for pixel in list(image): 13 | if pixel[i] > j: 14 | image.remove(pixel) 15 | pixel = list(pixel) 16 | pixel[i] = 2 * j - pixel[i] 17 | image.add(tuple(pixel)) 18 | 19 | if first: 20 | print(len(image)) 21 | first = False 22 | 23 | for b in range(ab[1]): 24 | for a in range(ab[0]): 25 | print(" #"[(a, b) in image], end="") 26 | print() 27 | -------------------------------------------------------------------------------- /2021/14.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import Counter 3 | 4 | template, _, *rules = open(0).read().splitlines() 5 | rules = dict(rule.split(" -> ") for rule in rules) 6 | pairs, elements = Counter(map(str.__add__, template, template[1:])), Counter(template) 7 | 8 | for i in 10, 30: 9 | for _ in range(i): 10 | for (a, b), count in pairs.copy().items(): 11 | c = rules[a + b] 12 | pairs[a + b] -= count 13 | pairs[a + c] += count 14 | pairs[c + b] += count 15 | elements[c] += count 16 | 17 | print(max(elements.values()) - min(elements.values())) 18 | -------------------------------------------------------------------------------- /2021/15.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from heapq import heappop, heappush 3 | 4 | m = [list(map(int, line)) for line in open(0).read().splitlines()] 5 | height, width = len(m), len(m[0]) 6 | 7 | # Fast Dijkstra version 8 | for i in 1, 5: 9 | heap, seen = [(0, 0, 0)], {(0, 0)} 10 | 11 | while heap: 12 | risk, r, c = heappop(heap) 13 | if r == i * height - 1 and c == i * width - 1: 14 | print(risk) 15 | break 16 | 17 | for r_, c_ in (r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1): 18 | if 0 <= r_ < i * height and 0 <= c_ < i * width and (r_, c_) not in seen: 19 | rd, rm = divmod(r_, height) 20 | cd, cm = divmod(c_, width) 21 | 22 | seen.add((r_, c_)) 23 | heappush(heap, (risk + (m[rm][cm] + rd + cd - 1) % 9 + 1, r_, c_)) 24 | 25 | # Slower NetworkX version 26 | import networkx as nx 27 | 28 | for i in 1, 5: 29 | g = nx.grid_2d_graph(i * height, i * width, create_using=nx.DiGraph) 30 | 31 | for _, (x, y), data in g.edges(data=True): 32 | xd, xm = divmod(x, height) 33 | yd, ym = divmod(y, width) 34 | data["weight"] = (m[xm][ym] + xd + yd - 1) % 9 + 1 35 | 36 | print( 37 | nx.shortest_path_length( 38 | g, source=(0, 0), target=(i * height - 1, i * width - 1), weight="weight" 39 | ) 40 | ) 41 | -------------------------------------------------------------------------------- /2021/16.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from operator import add, mul, gt, lt, eq 3 | 4 | 5 | def parse(line): 6 | bits = ((int(c, 16) >> i) & 1 for c in line for i in range(3, -1, -1)) 7 | ops = add, mul, lambda *x: min(x), lambda *x: max(x), None, gt, lt, eq 8 | pos = ver = 0 9 | 10 | def read(size): 11 | nonlocal pos 12 | pos += size 13 | return sum(next(bits) << i for i in range(size - 1, -1, -1)) 14 | 15 | def packet(): 16 | nonlocal ver 17 | ver += read(3) 18 | 19 | if (type_id := read(3)) == 4: 20 | go, total = read(1), read(4) 21 | while go: 22 | go, total = read(1), total << 4 | read(4) 23 | elif read(1) == 0: 24 | length = read(15) + pos 25 | total = packet() 26 | while pos < length: 27 | total = ops[type_id](total, packet()) 28 | else: 29 | count = read(11) 30 | total = packet() 31 | for _ in range(count - 1): 32 | total = ops[type_id](total, packet()) 33 | 34 | return total 35 | 36 | total = packet() 37 | 38 | return ver, total 39 | 40 | 41 | ver, total = parse(input()) 42 | print(ver) 43 | print(total) 44 | -------------------------------------------------------------------------------- /2021/17.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import defaultdict 3 | import re 4 | 5 | xmin, xmax, ymin, ymax = map(int, re.findall(r"-?\d+", input())) 6 | print((ymin + 1) * ymin // 2) 7 | 8 | v, n = 0, int((xmin * 2) ** 0.5 - 1) # n-th member of arithmetic progression 9 | 10 | # Fast, precomputed steps version 11 | dxs = defaultdict(set) 12 | for dx_init in range(n, xmax + 1): 13 | x, dx, step = 0, dx_init, 0 14 | while x <= xmax and (dx == 0 and xmin <= x or dx != 0): 15 | x += dx 16 | # fmt:off 17 | if dx > 0: dx -= 1 18 | # fmt:on 19 | step += 1 20 | if xmin <= x <= xmax: 21 | dxs[dx_init].add(step) 22 | if dx == 0: 23 | dxs[dx_init] = min(dxs[dx_init]) 24 | break 25 | 26 | dys = defaultdict(set) 27 | for dy_init in range(ymin, -ymin): 28 | y, dy, step = 0, dy_init, 0 29 | while ymin <= y: 30 | y += dy 31 | dy -= 1 32 | step += 1 33 | if ymin <= y <= ymax: 34 | dys[dy_init].add(step) 35 | 36 | for xsteps in dxs.values(): 37 | for ysteps in dys.values(): 38 | if type(xsteps) is int: 39 | if xsteps <= max(ysteps): 40 | v += 1 41 | elif xsteps & ysteps: 42 | v += 1 43 | 44 | # Slower, brute force version 45 | # 46 | # for dy_init in range(ymin, -ymin): 47 | # for dx_init in range(n, xmax + 1): 48 | # x, y, dx, dy = 0, 0, dx_init, dy_init 49 | # while x <= xmax and y >= ymin and (dx == 0 and xmin <= x or dx != 0): 50 | # x += dx 51 | # y += dy 52 | # # fmt:off 53 | # if dx > 0: dx -= 1 54 | # # fmt:on 55 | # dy -= 1 56 | # if xmin <= x <= xmax and ymin <= y <= ymax: 57 | # v += 1 58 | # break 59 | 60 | print(v) 61 | -------------------------------------------------------------------------------- /2022/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | c = sorted(sum(map(int, e.splitlines())) for e in open(0).read().split("\n\n")) 3 | print(c[-1]) 4 | print(sum(c[-3:])) 5 | -------------------------------------------------------------------------------- /2022/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | s1 = s2 = 0 3 | 4 | for i, j in map(str.split, open(0).read().splitlines()): 5 | i, j = ord(i) - ord("A"), ord(j) - ord("X") 6 | s1 += 1 + j + (j - i + 1) % 3 * 3 7 | s2 += 1 + j * 3 + (j + i - 1) % 3 8 | 9 | print(s1) 10 | print(s2) 11 | -------------------------------------------------------------------------------- /2022/03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | lines = open(0).read().splitlines() 3 | # fmt:off 4 | print(sum( 5 | (ord(x) - ord("a")) % 58 + 1 6 | for l in lines 7 | for x in set(l[: len(l) // 2]) & set(l[len(l) // 2 :]) 8 | )) 9 | 10 | print(sum( 11 | (ord(x) - ord("a")) % 58 + 1 12 | for a, b, c in zip(*[iter(lines)] * 3) 13 | for x in set(a) & set(b) & set(c) 14 | )) 15 | -------------------------------------------------------------------------------- /2022/04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | s1 = s2 = 0 3 | 4 | for l in open(0): 5 | a1, a2, b1, b2 = map(int, l.replace(",", "-").split("-")) 6 | s1 += a1 <= b1 and b2 <= a2 or b1 <= a1 and a2 <= b2 7 | s2 += b1 <= a2 and a1 <= b2 8 | 9 | print(s1) 10 | print(s2) 11 | -------------------------------------------------------------------------------- /2022/05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | def solve(stacks, lines, step=1): 3 | for count, src, dst in lines: 4 | stacks[dst] += stacks[src][-count:][::step] 5 | stacks[src] = stacks[src][:-count] 6 | 7 | return "".join(s[-1] for s in stacks) 8 | 9 | 10 | data, lines = open(0).read().split("\n\n") 11 | stacks = [ 12 | "".join(column).rstrip() 13 | for i, column in enumerate(zip(*data.splitlines()[-2::-1])) 14 | if i % 4 == 1 15 | ] 16 | lines = [ 17 | (int(line[1]), int(line[3]) - 1, int(line[5]) - 1) 18 | for line in map(str.split, lines.splitlines()) 19 | ] 20 | 21 | print(solve(stacks.copy(), lines, -1)) 22 | print(solve(stacks.copy(), lines)) 23 | -------------------------------------------------------------------------------- /2022/06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | data = input() 3 | for size in (4, 14): 4 | for i in range(size, len(data)): 5 | if len(set(data[i - size : i])) == size: 6 | print(i) 7 | break 8 | 9 | # Less elegant, but way faster than "set()" approach 10 | for size in (4, 14): 11 | distance, last_seen = size, {} 12 | 13 | for i, c in enumerate(data, start=1): 14 | if distance >= (d := i - last_seen.get(c, i - size)): 15 | distance = d 16 | else: 17 | distance += 1 18 | 19 | if distance >= size and i >= size: 20 | print(i) 21 | break 22 | 23 | last_seen[c] = i 24 | -------------------------------------------------------------------------------- /2022/07.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import defaultdict 3 | from itertools import accumulate 4 | 5 | dirs = defaultdict(int) 6 | 7 | for line in open(0).read().splitlines(): 8 | match line.split(): 9 | case "$", "cd", "/": 10 | path = ["/"] 11 | case "$", "cd", "..": 12 | path.pop() 13 | case "$", "cd", dir: 14 | path.append(dir + "/") 15 | case "$" | "dir", *_: 16 | continue 17 | case size, _: 18 | for p in accumulate(path): 19 | dirs[p] += int(size) 20 | 21 | print(sum(size for size in dirs.values() if size <= 100_000)) 22 | print(min(size for size in dirs.values() if size >= dirs["/"] - 40_000_000)) 23 | -------------------------------------------------------------------------------- /2022/08.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | t = [list(map(int, l)) for l in open(0).read().splitlines()] 3 | height, width = len(t), len(t[0]) 4 | # fmt:off 5 | print(2 * height + 2 * width - 4 + sum( 6 | ( 7 | all(t[row][col] > t[row][c ] for c in range(0, col)) 8 | or all(t[row][col] > t[row][c ] for c in range(col + 1, width)) 9 | or all(t[row][col] > t[r ][col] for r in range(0, row)) 10 | or all(t[row][col] > t[r ][col] for r in range(row + 1, height)) 11 | ) 12 | for row in range(1, height - 1) 13 | for col in range(1, width - 1) 14 | )) 15 | # fmt:on 16 | max_score = 0 17 | 18 | for row in range(1, height - 1): 19 | for col in range(1, width - 1): 20 | score = 1 21 | 22 | for x, y in ((0, -1), (0, 1), (1, 0), (-1, 0)): 23 | r, c, dist = row + x, col + y, 0 24 | 25 | while 0 <= r < height and 0 <= c < width: 26 | dist += 1 27 | if t[row][col] <= t[r][c]: 28 | break 29 | r += x 30 | c += y 31 | 32 | score *= dist 33 | max_score = max(max_score, score) 34 | 35 | print(max_score) 36 | -------------------------------------------------------------------------------- /2022/09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | dirs = {"U": 1j, "D": -1j, "L": -1, "R": 1} 3 | sign = lambda a: (a > 0) - (a < 0) 4 | rope, s1, s2 = [0] * 10, {0}, {0} 5 | 6 | for line in open(0).read().splitlines(): 7 | d, s = line.split() 8 | 9 | for _ in range(int(s)): 10 | rope[0] += dirs[d] 11 | 12 | for i in range(1, len(rope)): 13 | if abs(diff := rope[i - 1] - rope[i]) >= 2: 14 | rope[i] += complex(sign(diff.real), sign(diff.imag)) 15 | 16 | s1.add(rope[1]) 17 | s2.add(rope[-1]) 18 | 19 | print(len(s1)) 20 | print(len(s2)) 21 | -------------------------------------------------------------------------------- /2022/10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | x, signal, crt = 1, 0, "" 3 | 4 | for cycle, ins in enumerate(open(0).read().split(), start=1): 5 | signal += cycle * x if cycle % 40 == 20 else 0 6 | crt += "\u2593" if (cycle - 1) % 40 - x in (-1, 0, 1) else "\u2591" 7 | 8 | if ins[-1].isdigit(): # workaround for negative numbers 9 | x += int(ins) 10 | 11 | print(signal) 12 | 13 | for i in range(0, len(crt), 40): 14 | print(crt[i : i + 40]) 15 | -------------------------------------------------------------------------------- /2022/11.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import deque 3 | from math import lcm, prod 4 | from operator import floordiv, mod 5 | 6 | ITEMS, OPER, TEST, A, B = range(5) 7 | 8 | 9 | def solve(monkeys, iterations, op, number): 10 | inspections = [0] * len(monkeys) 11 | 12 | for _ in range(iterations): 13 | for i, m in enumerate(monkeys): 14 | while m[ITEMS]: 15 | inspections[i] += 1 16 | item = op(m[OPER](m[ITEMS].popleft()), number) 17 | if item % m[TEST] == 0: 18 | monkeys[m[A]][ITEMS].append(item) 19 | else: 20 | monkeys[m[B]][ITEMS].append(item) 21 | 22 | return prod(sorted(inspections)[-2:]) 23 | 24 | 25 | monkeys = [ 26 | [ 27 | deque(map(int, item[ITEMS].replace(",", "").split()[2:])), 28 | eval("lambda old: " + item[OPER].split(" = ")[-1]), 29 | int(item[TEST].split()[-1]), 30 | int(item[A].split()[-1]), 31 | int(item[B].split()[-1]), 32 | ] 33 | for block in open(0).read().split("\n\n") 34 | for item in [block.splitlines()[1:]] 35 | ] 36 | 37 | print(solve(monkeys, 20, floordiv, 3)) 38 | print(solve(monkeys, 10_000, mod, lcm(*(m[TEST] for m in monkeys)))) # Use least common multiple 39 | -------------------------------------------------------------------------------- /2022/12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import networkx as nx 3 | 4 | t = { 5 | k: ord(v) - ord("a") 6 | for r, line in enumerate(open(0)) 7 | for c, v in enumerate(line.strip()) 8 | for k in [complex(r, c)] 9 | if (v == "S" and (s := k)) or (v == "E" and (e := k)) or True 10 | } 11 | t[s], t[e] = 0, ord("z") - ord("a") 12 | G = nx.DiGraph( 13 | incoming_graph_data=( 14 | (k, n) 15 | for k, v in t.items() 16 | for d in (-1, 1, -1j, 1j) 17 | if (n := k + d) in t and t[n] - v <= 1 18 | ) 19 | ) 20 | paths = list(nx.single_target_shortest_path_length(G, e)) 21 | print(next(v for k, v in paths if k == s)) 22 | print(min(v for k, v in paths if t[k] == 0)) 23 | 24 | ## BFS example, but slower than networkx 25 | # 26 | # from collections import deque 27 | # 28 | # 29 | # def bfs(t, s, e): 30 | # queue, seen = deque([(s, 0)]), set([s]) 31 | # 32 | # while queue: 33 | # k, size = queue.popleft() 34 | # 35 | # if k == e: 36 | # return size 37 | # 38 | # for d in (-1, 1, -1j, 1j): 39 | # if (n := k + d) in t and n not in seen and t[n] - t[k] <= 1: 40 | # queue.append((n, size + 1)) 41 | # seen.add(n) 42 | # 43 | # 44 | # print(bfs(t, s, e)) 45 | -------------------------------------------------------------------------------- /2022/13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | def cmp(left, right): 3 | match left, right: 4 | case int(), list(): 5 | return cmp([left], right) 6 | case list(), int(): 7 | return cmp(left, [right]) 8 | case int(), int(): 9 | return left - right 10 | case list(), list(): 11 | for i, j in zip(left, right): 12 | if (r := cmp(i, j)) != 0: 13 | return r 14 | return cmp(len(left), len(right)) 15 | 16 | 17 | # fmt:off 18 | s1, two, six, packets = 0, 1, 2, [ 19 | eval(line) 20 | for line in open(0).read().splitlines() 21 | if len(line) 22 | ] 23 | # fmt:on 24 | for i, (left, right) in enumerate(zip(packets[::2], packets[1::2])): 25 | if cmp(left, right) <= 0: 26 | s1 += i + 1 27 | for x in left, right: 28 | two += cmp(x, [[2]]) <= 0 29 | six += cmp(x, [[6]]) <= 0 30 | 31 | print(s1) 32 | print(two * six) 33 | -------------------------------------------------------------------------------- /2022/14.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from itertools import pairwise 3 | from collections import deque 4 | 5 | 6 | # Kudos to https://www.reddit.com/r/adventofcode/comments/zli1rd/comment/j061f6z 7 | def solve(check, path=deque([500])): 8 | while True: 9 | pos = path[-1] 10 | for dst in pos + 1j, pos - 1 + 1j, pos + 1 + 1j: 11 | if dst not in blocked and dst.imag < bottom + 2: 12 | path.append(dst) 13 | break 14 | else: 15 | if check(pos): 16 | return len(blocked) - rocks 17 | blocked.add(pos) 18 | path.pop() 19 | 20 | 21 | blocked = { 22 | complex(x, y) 23 | for line in open(0).read().splitlines() 24 | for (x1, y1), (x2, y2) in pairwise(map(eval, line.split("->"))) 25 | for x in range(min(x1, x2), max(x1, x2) + 1) 26 | for y in range(min(y1, y2), max(y1, y2) + 1) 27 | } 28 | 29 | rocks, bottom = len(blocked), int(max(i.imag for i in blocked)) 30 | 31 | print(solve(lambda pos: pos.imag > bottom)) 32 | print(solve(lambda pos: pos == 500) + 1) 33 | -------------------------------------------------------------------------------- /2022/21.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | def solve(e): 3 | actions = m[e] 4 | if len(actions) == 1: 5 | return actions[0] 6 | 7 | a, op, b = actions 8 | return "(" + solve(a) + op + solve(b) + ")" 9 | 10 | 11 | m = { 12 | monkey: actions.split(" ") 13 | for line in open(0).read().splitlines() 14 | for monkey, actions in [line.split(": ")] 15 | } 16 | 17 | print(int(eval(solve("root")))) 18 | 19 | # Kudos to https://www.reddit.com/r/adventofcode/comments/zrav4h/comment/j133ko6/ 20 | m["humn"], m["root"][1] = ["-1j"], "-(" 21 | c = eval(solve("root") + ")") 22 | print(int(c.real / c.imag)) 23 | 24 | # # Using z3 solver 25 | # 26 | # import z3 27 | # 28 | # m["humn"], m["root"][1] = ["humn"], "==" 29 | # 30 | # humn = z3.Int("humn") 31 | # s = z3.Solver() 32 | # s.add(eval(solve("root"))) 33 | # s.check() 34 | # print(s.model()[humn]) 35 | -------------------------------------------------------------------------------- /2022/23.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from itertools import count 3 | from collections import defaultdict, deque 4 | 5 | 6 | def solve(elves, dirs, neighbors): 7 | moves, moved = defaultdict(list), False 8 | 9 | for e in elves: 10 | if all(e + n not in elves for n in neighbors): 11 | moves[e].append(e) 12 | else: 13 | moved = True 14 | for dir in dirs: 15 | if all(e + n not in elves for n in dir): 16 | moves[e + dir[0]].append(e) 17 | break 18 | else: 19 | moves[e].append(e) 20 | 21 | dirs.rotate(-1) 22 | 23 | elves = set() 24 | for k, v in moves.items(): 25 | if len(v) == 1: # Only one candidate for the position, using the new position 26 | elves.add(k) 27 | else: # Multiple candidates, using their original positions instead 28 | elves.update(v) 29 | 30 | return elves, moved 31 | 32 | 33 | elves = { 34 | complex(row, col) 35 | for row, line in enumerate(open(0).read().splitlines()) 36 | for col, x in enumerate(line) 37 | if x == "#" 38 | } 39 | # fmt: off 40 | dirs = deque([(-1, -1+1j, -1-1j), (1, 1+1j, 1-1j), (-1j, -1-1j, 1-1j), (1j, -1+1j, 1+1j)]) 41 | neighbors = (-1, 1, -1j, 1j, -1-1j, -1+1j, 1-1j, 1+1j) 42 | # fmt: on 43 | for i in count(1): 44 | elves, moved = solve(elves, dirs, neighbors) 45 | 46 | if i == 10: 47 | rows, cols = {e.real for e in elves}, {e.imag for e in elves} 48 | print(int((max(rows) - min(rows) + 1) * (max(cols) - min(cols) + 1) - len(elves))) 49 | 50 | if moved == False: 51 | print(i) 52 | break 53 | -------------------------------------------------------------------------------- /2022/24.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | def solve(start, stop, step): 3 | positions = set([start]) 4 | 5 | while True: 6 | next_positions = set() 7 | for r, c in positions: 8 | for x, y in ((r, c), (r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)): 9 | if (x, y) == stop: 10 | return step 11 | # fmt:off 12 | if 0 <= x < height and 0 <= y < width \ 13 | and grid[x][(y - step) % width] != ">" \ 14 | and grid[x][(y + step) % width] != "<" \ 15 | and grid[(x - step) % height][y] != "v" \ 16 | and grid[(x + step) % height][y] != "^": 17 | next_positions.add((x, y)) 18 | # fmt:on 19 | positions = next_positions 20 | if not positions: 21 | positions.add(start) 22 | step += 1 23 | 24 | 25 | grid = [row[1:-1] for row in open(0).read().splitlines()[1:-1]] 26 | height, width = len(grid), len(grid[0]) 27 | start, stop = (-1, 0), (height, width - 1) 28 | 29 | print(s1 := solve(start, stop, 1)) 30 | print(solve(start, stop, solve(stop, start, s1))) 31 | -------------------------------------------------------------------------------- /2022/25.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | SNAFU = "=-012" 3 | 4 | s1, s = "", sum( 5 | sum(5**i * (SNAFU.index(c) - 2) for i, c in enumerate(n[::-1])) 6 | for n in open(0).read().splitlines() 7 | ) 8 | 9 | while s: 10 | s, m = divmod(s + 2, 5) 11 | s1 = SNAFU[m] + s1 12 | 13 | print(s1) 14 | -------------------------------------------------------------------------------- /2023/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | 4 | lines = open(0).read().splitlines() 5 | # fmt:off 6 | digits = {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7, "eight": 8, "nine": 9} 7 | # fmt:on 8 | keys = "|".join(digits.keys()) 9 | 10 | 11 | def solve(lines, r_first, r_last): 12 | return sum( 13 | # fmt:off 14 | digits[r_first.search(line)[1]] * 10 + digits[r_last.search(line)[1]] 15 | for line in lines 16 | # fmt:on 17 | ) 18 | 19 | 20 | print(solve(lines, re.compile(r"(\d)"), re.compile(r".*(\d)"))) 21 | print(solve(lines, re.compile(rf"({keys})"), re.compile(rf".*({keys})"))) 22 | -------------------------------------------------------------------------------- /2023/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from math import prod 4 | 5 | s1, s2, balls, r = 0, 0, {"red": 12, "green": 13, "blue": 14}, re.compile(r"(\d+) (\w+)") 6 | 7 | for i, line in enumerate(open(0).read().splitlines(), start=1): 8 | maxes = {"red": 0, "green": 0, "blue": 0} 9 | 10 | for count, color in r.findall(line): 11 | maxes[color] = max(maxes[color], int(count)) 12 | 13 | s1 += i * all(balls[color] >= count for color, count in maxes.items()) 14 | s2 += prod(maxes.values()) 15 | 16 | print(s1) 17 | print(s2) 18 | -------------------------------------------------------------------------------- /2023/03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from collections import defaultdict 4 | from math import prod 5 | 6 | 7 | def parse(lines): 8 | re_number = re.compile(r"\d+") 9 | re_symbol = re.compile(r"[^\d.]") 10 | 11 | for y, line in enumerate(lines): 12 | for m in re_number.finditer(line): 13 | number, x0, x1 = int(m.group()), m.start() - 1, m.end() + 1 14 | for yy in range(max(y - 1, 0), min(y + 2, len(lines))): 15 | for m in re_symbol.finditer(lines[yy], x0, x1): 16 | yield number, m.group(), m.start(), yy 17 | 18 | 19 | s1, gears = 0, defaultdict(list) 20 | 21 | for number, symbol, x, y in parse(open(0).read().splitlines()): 22 | s1 += number 23 | if symbol == "*": 24 | gears[x, y].append(number) 25 | 26 | print(s1) 27 | print(sum(prod(numbers) for numbers in gears.values() if len(numbers) == 2)) 28 | -------------------------------------------------------------------------------- /2023/04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | lines = open(0).read().splitlines() 3 | s1, copies = 0, [1] * len(lines) 4 | 5 | for i, line in enumerate(lines): 6 | matches = len(set.intersection(*map(set, map(str.split, line.split(":")[1].split("|"))))) 7 | s1 += 2 ** (matches - 1) if matches else 0 8 | for j in range(matches): 9 | copies[i + j + 1] += copies[i] 10 | 11 | print(s1) 12 | print(sum(copies)) 13 | -------------------------------------------------------------------------------- /2023/05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import deque 3 | 4 | seeds, *maps = open(0).read().split("\n\n") 5 | seeds = list(map(int, seeds.split(":")[1].split())) 6 | maps = [ 7 | sorted( 8 | [ 9 | (src, src + length, dst - src) 10 | for line in m.splitlines()[1:] 11 | for dst, src, length in [map(int, line.split())] 12 | ] 13 | ) 14 | for m in maps 15 | ] 16 | # print(seeds, maps, len(maps)) 17 | 18 | 19 | def solve(s): 20 | for m in maps: 21 | for start, stop, offset in m: 22 | if start <= s < stop: 23 | s += offset 24 | break 25 | return s 26 | 27 | 28 | def solve2(seeds): 29 | queue = deque([(a, a + b, 0) for a, b in zip(seeds[::2], seeds[1::2])]) 30 | print(queue) 31 | 32 | while queue: 33 | a, b, i = queue.popleft() 34 | # print(a, b, i) 35 | 36 | if i == len(maps) - 1: 37 | yield a 38 | yield b 39 | else: 40 | m = maps[i] 41 | 42 | for start, stop, offset in m: 43 | if start < b and a < stop: 44 | queue.append((max(start, a) + offset, min(stop, b) + offset, i + 1)) 45 | 46 | first, last = m[0][0], m[-1][1] 47 | if b <= first or last <= a: 48 | queue.append((a, b, i + 1)) 49 | else: 50 | if a < first < b: 51 | queue.append((a, first, i + 1)) 52 | if a < last < b: 53 | queue.append((last, b, i + 1)) 54 | 55 | 56 | print(min(solve(s) for s in seeds)) 57 | print(min(solve2(seeds))) 58 | -------------------------------------------------------------------------------- /2024/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from collections import Counter 3 | 4 | left, right = zip(*(map(int, line.split()) for line in open(0))) 5 | rcounts = Counter(right) 6 | 7 | print(sum(abs(l - r) for l, r in zip(sorted(left), sorted(right)))) 8 | print(sum(l * rcounts[l] for l in left)) 9 | -------------------------------------------------------------------------------- /2024/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | reports = [[*map(int, line.split())] for line in open(0)] 3 | 4 | 5 | def safe(report, tolerate=0): 6 | for i in range(len(report) - 1): 7 | if not 1 <= (report[i] - report[i + 1]) <= 3: 8 | return tolerate and any(safe(report[j - 1 : j] + report[j + 1 :]) for j in (i, i + 1)) 9 | return True 10 | 11 | 12 | for tolerate in 0, 1: 13 | print(sum(safe(report, tolerate) or safe(report[::-1], tolerate) for report in reports)) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2 | 3 | [Advent of Code](http://adventofcode.com) solutions written in Python 3. 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | --------------------------------------------------------------------------------