Quite challenging for a day 1! Learned some new regex for part 2 which was fun - positive lookahead ?=... essentially means you can extract overlapping matches
withopen("aoc/1/input.txt", "r") as f: inp = f.readlines()ONE_TO_NINE =list(map(str, list(range(1, 10))))def extract_first_num(a):for char in a:if char in ONE_TO_NINE:return chartotal =0for row in inp: total +=int(extract_first_num(row) + extract_first_num(row[::-1]))print("Part 1 answer:")print(total)import redef convert_num(x): num_map =dict(zip( ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"], ONE_TO_NINE, ) )if x.isnumeric():return xelse:return num_map[x]total =0for row in inp: cap = re.findall(r"(?=(\d|one|two|three|four|five|six|seven|eight|nine))", row) total +=int(convert_num(cap[0]) + convert_num(cap[-1]))print("Part 2 answer:")print(total)
withopen("aoc/2/input.txt", "r") as f: inp = f.readlines()total =0for row in inp: index, game = row.split(":") index =int(index.replace("Game ", "")) possible =Truefor game in game.split(";"): bag_one =dict( red=12, green=13, blue=14, )for colours in game.split(","): num, color = colours.strip().split(" ")ifint(num) > bag_one[color]: possible =Falseif possible: total += indexprint("Part 1 answer:")print(total)import mathtotal =0for row in inp: index, game = row.split(":") index =int(index.replace("Game ", "")) bag_max =dict( red=0, green=0, blue=0, )for game in game.split(";"): for colours in game.split(","): num, color = colours.strip().split(" ")ifint(num) > bag_max[color]: bag_max[color] =int(num) total += math.prod(bag_max.values())print("Part 2 answer:")print(total)
I don’t like grids 🫠I probably made this harder than it needed to be. If I were to do this again I probably would have just used euclidian distance comparisons
import refrom collections import defaultdictfrom itertools import productwithopen("aoc/3/input.txt", "r") as f: inp = f.readlines()# examine all unique chars to populate regex pattern and SYMBOLS set# print(set(y for x in inp for y in x))total =0SYMBOLS = {"*", "#", "$", "+", "-", "%", "=", "/", "&", "@"}p = re.compile("\d+|\*|#|\$|\+|-|%|=|\/|&|@")grid = []syms = []def poss_neighbours(i: int, j: tuple): i_s =list(filter(lambda x: x >=0, [i, i-1, i+1])) j_s =list(filter(lambda x: x >=0and x <len(inp[0]), [*j, j[0]-1, j[-1]+1]))for out_i in i_s:for out_j in j_s:if out_i == i and out_j in j:continueyield out_i, out_jassertset(poss_neighbours(0, (0,))) == {(1,0), (1,1), (0,1)}# construct the data structures for iterating over numbers and symbolsfor i, row inenumerate(inp):for m in p.finditer(row): group = m.group() js =tuple(range(*m.span())) out = (group, i, js)if m.group() in SYMBOLS: syms.append( (group, i, js[0]) )else: grid.append( (int(group), i, js) ) # part 1 logicfor num, i, js in grid: poss =list(poss_neighbours(i, js))for _, sym_i, sym_j in syms:if (sym_i, sym_j) in poss: total += numprint("Part 1 answer:")print(total)import mathtotal =0# part 2 logicfor sym, i, j in syms:if sym =="*": poss =list(poss_neighbours(i, (j, ))) adj =set()for num, num_i, num_js in grid:for num_j in num_js:if (num_i, num_j) in poss: adj.add(num)iflen(adj) ==2: total += math.prod(adj)print("Part 2 answer:")print(total)
Enjoyed the logic for the second part with the copies. I’m sure there was potential to go on a wild goose chase with recursion here, so I’m happy to have avoided the temptation 🤣
withopen("aoc/4/input.txt", "r") as f: inp = f.readlines()total =0# part 1 logicfor row in inp: index, rest = row.split(":") win, play = rest.split("|") win =list(filter(lambda x: x.isnumeric(), win.strip().split(" "))) play =list(filter(lambda x: x.isnumeric(), play.strip().split(" "))) score =0for num in win:if num in play: score +=1if score >0: total +=2** (score -1)print("Part 1 answer:")print(total)total =0copies = {}# part 2 logicfor row in inp: index, rest = row.split(":") index =int(index.split("d")[1].strip()) win, play = rest.split("|") win =list(filter(lambda x: x.isnumeric(), win.strip().split(" "))) play =list(filter(lambda x: x.isnumeric(), play.strip().split(" "))) score =0for num in win:if num in play: score +=1for x inrange(index+1, index+score+1): copies[x] = copies.get(x, 0) + copies.get(index, 0) +1 total += copies.get(index, 0) +1print("Part 2 answer:")print(total)
from collections import OrderedDictfrom typing import Anywithopen("aoc/5/input.txt", "r") as f: inp = f.readlines()# create mapsseeds =list(map(int, inp.pop(0).replace("seeds: ", "").strip().split(" ")))maps = OrderedDict()class Mapper:def__init__(self, dest, source, rng):self.dest = destself.source = sourceself.rng = rngdef check(self, x):returnself.source <= x < (self.source +self.rng)def__call__(self, x) -> Any:return x + (self.dest -self.source)def__repr__(self):returnf"{self.dest=}|{self.source=}|{self.rng=}"for line in inp:if line =="\n":continueif"map"in line: map_name = line.replace("\n", "").replace(" map:", "")else: dest, source, rng =map(int, line.replace("\n", "").split(" ")) maps.setdefault(map_name, []).append( Mapper(dest, source, rng) )locations = []for x in seeds:print("seed:", x)for k in maps.keys(): current_map = maps[k]for f in current_map:if f.check(x): x = f(x)break locations.append(x)print("Part 1 answer:")print(min(locations))# This is incredibly slow as too many ints to check!# Need to try another approach using ranges!# locations = []# for z, y in zip(seeds[::2], seeds[1::2]):# for x in range(z, z+y):# # print("seed:", x)# for k in maps.keys():# current_map = maps[k]# for f in current_map:# if f.check(x):# x = f(x)# break# locations.append(x)# print("Part 2 answer:")# print(min(locations))
Tried to do part 2 a silly way at first - I didn’t realise that all z visits happen on the same cycle for a given starting point!
withopen("aoc/8/input.txt", "r") as f: inp = f.readlines()class Node:def__init__(self, name, left, right):self.name = nameself.L = leftself.R = right def transition(self, direction):returngetattr(self, direction) total =0ins = inp[0].replace("\n", "")all_nodes = []for row in inp[2:]: start, rest = row.split("=") start = start.strip() left, right = rest.split(",") left = left.replace("(","").strip() right = right.replace(")", "").strip() all_nodes.append( Node(start, left, right) )current_node = [x for x in all_nodes if x.name =="AAA"][0]i =0whileTrue: direction = ins[i %len(ins)] current_node = [ x for x in all_nodes if x.name == current_node.transition(direction) ][0] i +=1if current_node.name =="ZZZ":breaktotal = iprint("Part 1 answer:")print(total)current_nodes = [x for x in all_nodes if x.name[-1] =="A"]totals = []for current_node in current_nodes: i =0whileTrue: direction = ins[i %len(ins)] current_node = [ x for x in all_nodes if x.name == current_node.transition(direction) ][0] i +=1if current_node.name[-1] =="Z": totals.append(i)breakimport mathprint("Part 2 answer:")print(math.lcm(*totals))
Part 1 answer:
18023
Part 2 answer:
14449445933179
Learned about Lagrangian Interpolation which was …fun? In a slightly-too-small-nutshell, we can construct a summation of polynomials, where each term goes to zero when its corresponding x value is inputted to the function. Each term will be in the form:
Where \(n\) is the order of the term (constant, x, x^2 etc.) and \(\text{diff}_n\) is the difference at the correct level in the sequence of inputs. The differencing table is essentially what is constructed in the puzzle explanation. From this table, we take the first column of values to use as \(diff\).
import mathwithopen("aoc/9/input.txt", "r") as f: inp = f.readlines()total =0def calculate_next(diff_col, x): n =len(diff_col) total =0for i, diff inzip(range(n), diff_col): total += diff / math.factorial(i) * math.prod(x - ns for ns inrange(1,i+1))return totaloutputs = []for row in inp: seq =list(map(int, row.split(" "))) diffs = [seq]whileTrue: diffs.append([y - x for x,y inzip(diffs[-1], diffs[-1][1:])])iflen(set(diffs[-1])) <=1:break outputs.append(calculate_next([x[0] for x in diffs], len(seq) +1))print("Part 1 answer:")print(int(sum(outputs)))outputs = []for row in inp: seq =list(map(int, row.split(" "))) diffs = [seq]whileTrue: diffs.append([y - x for x,y inzip(diffs[-1], diffs[-1][1:])])iflen(set(diffs[-1])) <=1:break outputs.append(calculate_next([x[0] for x in diffs], 0))print("Part 2 answer:")print(int(sum(outputs)))