summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorBrian Chu <brianmchu42@gmail.com>2021-12-30 15:11:21 -0800
committerBrian Chu <brianmchu42@gmail.com>2021-12-30 15:11:21 -0800
commite7085453864431ace3ad8f3123b259ed0829ae74 (patch)
tree2ef1fbb0e9d02fc934b5e09d96dd187f3e371ea6
downloadAdventOfCode2015-main.tar.gz
all solutions for 2015 main
-rw-r--r--.gitignore1
-rw-r--r--day1.py17
-rw-r--r--day10.py12
-rw-r--r--day11.py42
-rw-r--r--day12.py28
-rw-r--r--day13.py24
-rw-r--r--day14.py29
-rw-r--r--day15.py31
-rw-r--r--day16.py44
-rw-r--r--day17.py25
-rw-r--r--day18.py33
-rw-r--r--day19.py28
-rw-r--r--day2.py23
-rw-r--r--day20.py21
-rw-r--r--day21.py59
-rw-r--r--day22.fsx72
-rw-r--r--day23.py44
-rw-r--r--day24.fsx19
-rw-r--r--day25.py10
-rw-r--r--day3.py53
-rw-r--r--day4.py21
-rw-r--r--day5.py34
-rw-r--r--day6.py39
-rw-r--r--day7.py49
-rw-r--r--day8.py34
-rw-r--r--day9.py29
26 files changed, 821 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2211df6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.txt
diff --git a/day1.py b/day1.py
new file mode 100644
index 0000000..aa328b9
--- /dev/null
+++ b/day1.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+with open('day1.txt') as data:
+    floors = data.read().strip()
+# part 1
+print(sum(map(lambda x: 1 if x == '(' else -1, floors)))
+
+# part 2
+floor = 0
+for i, char in enumerate(floors):
+    if char == '(':
+        floor += 1
+    else:
+        floor -= 1
+    if floor == -1:
+        print(i+1)
+        break
diff --git a/day10.py b/day10.py
new file mode 100644
index 0000000..c135c81
--- /dev/null
+++ b/day10.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+from itertools import groupby
+
+digits = "1113122113"
+
+for i in range(50):
+    digits = ''.join(str(len(list(g))) + str(k) for k, g in groupby(digits))
+    if i == 39:
+        print(len(digits))
+
+print(len(digits))
diff --git a/day11.py b/day11.py
new file mode 100644
index 0000000..534b6c4
--- /dev/null
+++ b/day11.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+from itertools import combinations
+
+def valid(password):
+    def windows():
+        alphabet = 'abcdefghijklmnopqrstuvwxyz'
+        for i in range(24):
+            yield alphabet[i:i+3]
+
+    def pairs():
+        alphabet = 'abcdefghijklmnopqrstuvwxyz'
+        for combo in combinations(alphabet, 2):
+            yield combo
+
+    if not any(seq in password for seq in windows()):
+        return False
+    if any(char in password for char in 'iol'):
+        return False
+    if not any(char1*2 in password and char2*2 in password for char1, char2 in pairs()):
+        return False
+    return True
+
+
+
+def passwords():
+    password = list(map(lambda x: ord(x)-96, 'cqjxjnds'))
+    while not password[0] == 27:
+        yield ''.join(char for char in map(lambda x: chr(x+96), password))
+        password[-1] += 1
+        for i in range(7, 0, -1):
+            if password[i] == 27:
+                password[i] = 1
+                password[i-1] += 1
+
+count = 0
+for password in passwords():
+    if valid(password):
+        print(password)
+        count += 1
+        if count == 2:
+            break
diff --git a/day12.py b/day12.py
new file mode 100644
index 0000000..90722ac
--- /dev/null
+++ b/day12.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+import re
+import json
+
+with open('day12.txt') as in_file:
+    data = in_file.read().strip()
+    nums = re.findall(r'-?\d+', data)
+    nums = map(int, nums)
+    # part 1
+    print(sum(nums))
+
+data = json.loads(data)
+
+def total_nums(obj):
+    if isinstance(obj, list):
+        return sum(map(total_nums, obj))
+    elif isinstance(obj, dict):
+        if any(val == 'red' for val in obj.values()):
+            return 0
+        else:
+            return sum(map(total_nums, obj.values()))
+    elif isinstance(obj, int):
+        return obj
+    else:
+        return 0
+
+print(total_nums(data))
diff --git a/day13.py b/day13.py
new file mode 100644
index 0000000..1f5bd23
--- /dev/null
+++ b/day13.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+from collections import defaultdict
+from itertools import permutations
+
+costs = defaultdict(lambda: defaultdict(int))
+
+with open('day13.txt') as data:
+    for line in data:
+        line = line.strip('.\n').split()
+        costs[line[0]][line[-1]] = int(line[3]) * (-1 if line[2] == 'lose' else 1)
+
+def calculate_cost(ordering):
+    return sum(costs[ordering[i-1]][ordering[i]] + costs[ordering[i]][ordering[i-1]]for i in range(len(ordering)))
+
+# part 1
+print(max(calculate_cost(order) for order in permutations(costs.keys())))
+
+# part 2
+for key in set(costs.keys()):
+    costs[key]['Myself'] = 0
+    costs['Myself'][key] = 0
+
+print(max(calculate_cost(order) for order in permutations(costs.keys())))
diff --git a/day14.py b/day14.py
new file mode 100644
index 0000000..a377fa8
--- /dev/null
+++ b/day14.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+from collections import defaultdict
+
+with open('day14.txt') as data:
+    racers = [tuple(map(int, (line[3], line[6], line[-2])))
+              for reindeer in data if (line := reindeer.strip().split())]
+
+def distance(reindeer, time):
+    speed, dur, rest = reindeer
+    total_distance = speed * dur * (time // (dur + rest))
+    remainder = time % (dur + rest)
+    total_distance += speed * remainder if remainder <= dur else speed * dur
+    return total_distance
+
+print(max(map(lambda x: distance(x, 2503), racers)))
+
+def points(reindeer, TOTAL_TIME):
+    scores = defaultdict(int)
+
+    for second in range(1, TOTAL_TIME+1):
+        distances = {deer: distance(deer, second) for deer in reindeer}
+        max_dist = max(distances.values())
+        for deer in distances:
+            if distances[deer] == max_dist:
+                scores[deer] += 1
+    return max(scores.values())
+
+print(points(racers, 2503))
diff --git a/day15.py b/day15.py
new file mode 100644
index 0000000..33dc8d3
--- /dev/null
+++ b/day15.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+import numpy as np
+
+properties = np.array([[3, 0, 0, -3, 2],
+                       [-3, 3, 0, 0, 9],
+                       [-1, 0, 4, 0, 1],
+                       [0, 0, -2, 2, 8]])
+
+def get_scores(count_calories=False):
+    max_score = 0
+    for sugar in range(100):
+        for sprinkles in range(100-sugar):
+            for candy in range(100-sugar-sprinkles):
+                chocolate = 100-sugar-sprinkles-candy
+                counts = np.array([sugar, sprinkles, candy, chocolate])
+                scores = properties.T @ counts
+                if count_calories and scores[-1] != 500:
+                    continue
+                if np.any(scores[:-1] <= 0):
+                    continue
+                else:
+                    score = np.prod(scores[:-1])
+                    max_score = max(max_score, score)
+    return max_score
+
+# part 1
+print(get_scores())
+
+# part 2
+print(get_scores(True))
diff --git a/day16.py b/day16.py
new file mode 100644
index 0000000..45cc6d3
--- /dev/null
+++ b/day16.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+from collections import defaultdict
+import re
+
+target = {
+    'children': 3,
+    'cats': 7,
+    'samoyeds': 2,
+    'pomeranians': 3,
+    'akitas': 0,
+    'vizslas': 0,
+    'goldfish': 5,
+    'trees': 3,
+    'cars': 2,
+    'perfumes': 1
+}
+
+info_re = re.compile(r'(\w+): (\d)')
+
+# part 1
+with open('day16.txt') as data:
+    for num, line in enumerate(data):
+        current = dict(map(lambda x: (x[0], int(x[1])), info_re.findall(line)))
+        if all(target[key] == current[key] for key in current):
+            print(num+1)
+            break
+
+# part 2
+def valid(curr):
+    for key in curr:
+        if key == 'cats' or key == 'trees':
+            yield curr[key] > target[key]
+        elif key == 'pomeranians' or key == 'goldfish':
+            yield curr[key] < target[key]
+        else:
+            yield curr[key] == target[key]
+
+with open('day16.txt') as data:
+    for num, line in enumerate(data):
+        current = dict(map(lambda x: (x[0], int(x[1])), info_re.findall(line)))
+        if all(valid(current)):
+            print(num+1)
+            break
diff --git a/day17.py b/day17.py
new file mode 100644
index 0000000..4d70a06
--- /dev/null
+++ b/day17.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+from itertools import combinations
+
+with open('day17.txt') as data:
+    containers = list(map(int, (line.strip() for line in data)))
+
+# part 1
+count = 0
+for i in range(2, len(containers)+1):
+    for combo in combinations(containers, i):
+        if sum(combo) == 150:
+            count += 1
+print(count)
+
+# part 2
+count = 0
+for i in range(2, len(containers)+1):
+    min_count = False
+    for combo in combinations(containers, i):
+        if sum(combo) == 150:
+            min_count = True
+            count += 1
+    if min_count:
+        break
+print(count)
diff --git a/day18.py b/day18.py
new file mode 100644
index 0000000..033cc76
--- /dev/null
+++ b/day18.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+import numpy as np
+from scipy.ndimage import convolve
+
+with open('day18.txt') as data:
+    grid = np.stack([list(map(lambda x: x == '#', line.strip())) for line in data]).astype(int)
+
+
+neighbors = [[1, 1, 1],
+            [1, 0, 1],
+            [1, 1, 1]]
+
+grid1 = np.copy(grid)
+for i in range(100):
+    neighbor_counts = convolve(grid1.astype(int), neighbors, mode='constant', cval=0)
+    survive = grid1 * (neighbor_counts >= 2) * (neighbor_counts <= 3)
+    grid1 = np.where(neighbor_counts == 3, True, survive)
+
+print(np.count_nonzero(grid1))
+
+grid2 = np.copy(grid)
+for coord in {(0,0), (0, 99), (99, 0), (99, 99)}:
+    grid2[coord] = True
+
+for i in range(100):
+    neighbor_counts = convolve(grid2.astype(int), neighbors, mode='constant', cval=0)
+    survive = grid2 * (neighbor_counts >= 2) * (neighbor_counts <= 3)
+    grid2 = np.where(neighbor_counts == 3, True, survive)
+    for coord in {(0,0), (0, 99), (99, 0), (99, 99)}:
+        grid2[coord] = True
+
+print(np.count_nonzero(grid2))
diff --git a/day19.py b/day19.py
new file mode 100644
index 0000000..d7b92ee
--- /dev/null
+++ b/day19.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+import re
+from collections import defaultdict
+
+replacements = []
+starter = None
+with open('day19.txt') as data:
+    for line in data:
+        groups = re.search(r'(\w+) => (\w+)', line)
+        if groups:
+            replacements.append(groups.groups((1, 2)))
+        else:
+            if line != '\n':
+                starter = line.strip()
+
+# part 1
+compounds = set()
+for elem, repl in replacements:
+    for i in range(len(starter)):
+        if starter[i:i+len(elem)] == elem:
+            compound = starter[:i] + repl + starter[i+len(elem):]
+            compounds.add(compound)
+print(len(compounds))
+
+# part 2
+elements = [s for s in re.split(r'([A-Z][^A-Z]*)', starter) if s]
+print(len(elements) - elements.count('Ar') - elements.count('Rn') - 2 * elements.count('Y') - 1)
diff --git a/day2.py b/day2.py
new file mode 100644
index 0000000..853e507
--- /dev/null
+++ b/day2.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+from itertools import combinations
+from math import prod
+
+with open('day2.txt') as data:
+    dims = [tuple(map(int, line.strip().split('x'))) for line in data]
+
+# part 1
+total_area = 0
+for dim in dims:
+    areas = combinations(dim, 2)
+    areas = list(map(lambda x: prod(x), areas))
+    total_area += 2 * sum(areas) + min(areas)
+
+print(total_area)
+
+# part 2
+total_length = 0
+for dim in dims:
+    wrap = 2 * sum(sorted(dim)[:2])
+    bow = prod(dim)
+    total_length += wrap + bow
+print(total_length)
diff --git a/day20.py b/day20.py
new file mode 100644
index 0000000..f8cf55d
--- /dev/null
+++ b/day20.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+from functools import reduce
+
+def divisors(n):
+    return set(reduce(list.__add__,
+                      ([i, n//i] for i in range(1, int(n**0.5) + 1) if n%i==0 )))
+
+n = 1
+total = 29000000
+while True:
+    if sum(divisors(n)) * 10 >= total:
+        print(n)
+        break
+    n += 1
+
+while True:
+    if sum(d for d in divisors(n) if n / d <= 50) * 11 >= total:
+        print(n)
+        break
+    n += 1
diff --git a/day21.py b/day21.py
new file mode 100644
index 0000000..c09cd9a
--- /dev/null
+++ b/day21.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+from itertools import product
+from collections import namedtuple
+from math import ceil
+
+Item = namedtuple('Item', ['cost', 'damage', 'armor'])
+Character = namedtuple('Character', ['hp', 'damage', 'armor'])
+weapons = [
+    Item(8, 4, 0),
+    Item(10, 5, 0),
+    Item(25, 6, 0),
+    Item(40, 7, 0),
+    Item(74, 8, 0)
+]
+
+armor = [
+    Item(0, 0, 0),
+    Item(13, 0, 1),
+    Item(31, 0, 2),
+    Item(53, 0, 3),
+    Item(75, 0, 4),
+    Item(102, 0, 5)
+]
+
+rings = [
+    Item(25, 1, 0),
+    Item(50, 2, 0),
+    Item(100, 3, 0),
+    Item(20, 0, 1),
+    Item(40, 0, 2),
+    Item(80, 0, 3),
+    Item(0, 0, 0),
+    Item(0, 0, 0)
+]
+
+def willWin(player, boss):
+    playerMoves = ceil(player.hp / max(boss.damage - player.armor, 1))
+    bossMoves = ceil(boss.hp / max(player.damage - boss.armor, 1))
+    return playerMoves >= bossMoves
+
+boss = Character(103, 9, 2)
+winningCosts = []
+losingCosts = []
+for weapon, shield, ring1, ring2 in product(weapons, armor, rings, rings):
+    if ring1.cost == ring2.cost and ring1.cost != 0: continue
+    totalCost = weapon.cost + shield.cost + ring1.cost + ring2.cost
+    totalDamage = weapon.damage + shield.damage + ring1.damage + ring2.damage
+    totalArmor = weapon.armor + shield.armor + ring1.armor + ring2.armor
+    player = Character(100, totalDamage, totalArmor)
+    if willWin(player, boss):
+        winningCosts.append(totalCost)
+    else:
+        losingCosts.append(totalCost)
+
+# part 1
+print(min(winningCosts))
+# part 2
+print(max(losingCosts))
diff --git a/day22.fsx b/day22.fsx
new file mode 100644
index 0000000..7944d25
--- /dev/null
+++ b/day22.fsx
@@ -0,0 +1,72 @@
+type Spell = 
+    { Name : string
+      Cost : int
+      Damage : int
+      Heal : int
+      Armor : int
+      Mana : int
+      Duration : int }
+
+let allSpells = 
+    [ { Name = "Magic Missile"; Cost = 53;  Damage = 4; Heal = 0; Armor = 0; Mana = 0;   Duration = 1}
+      { Name = "Drain";         Cost = 73;  Damage = 2; Heal = 2; Armor = 0; Mana = 0;   Duration = 1}
+      { Name = "Shield";        Cost = 113; Damage = 0; Heal = 0; Armor = 7; Mana = 0;   Duration = 6}
+      { Name = "Poison";        Cost = 173; Damage = 3; Heal = 0; Armor = 0; Mana = 0;   Duration = 6}
+      { Name = "Recharge";      Cost = 229; Damage = 0; Heal = 0; Armor = 0; Mana = 101; Duration = 5} ]
+
+let cheapestWin part (hp, mana) (bossHp, bossDamage) =
+    let rec play part myTurn best spent (hp, mana, spells) (bossHp, bossDamage) : int =
+        // if we have already spent more than the known cheapest win, bail
+        if spent >= best then best else
+
+        // part 2, check if I die before any effects play out
+        if part = 2 && myTurn && hp = 1 then best else
+
+        // apply effects
+        let mana  = (spells |> List.sumBy (fun s -> s.Mana)) + mana
+        let damage = spells |> List.sumBy (fun s -> s.Damage)
+        let armor  = spells |> List.sumBy (fun s -> s.Armor)
+
+        // does the boss die from effects?
+        let bossHp = bossHp - damage
+        if bossHp <= 0 then spent else
+
+        // decrement duration of all effects, and groom expired ones
+        let spells =
+            spells
+            |> List.map (fun s -> {s with Duration = s.Duration - 1})
+            |> List.filter (fun s -> s.Duration > 0)
+
+        if myTurn then
+            // part 2, I lose 1 HP on my turn
+            let hp = if part = 2 then hp - 1 else hp
+
+            // what spells can I afford and don't already have running?
+            match allSpells |> List.filter (fun s -> (s.Cost <= mana) && not (spells |> List.exists (fun s' -> s.Name = s'.Name))) with
+            | [] -> best
+            | buyableSpells ->
+                // play out the rest of the game with each possible purchase
+                let mutable newBest = best
+                for s in buyableSpells do
+                    let extraDamage, heal, spells = 
+                        if s.Duration = 1 then s.Damage, s.Heal, spells
+                        else 0, 0, s :: spells
+                    let spent = spent + s.Cost
+                    let mana = mana - s.Cost
+                    let hp = hp + heal
+                
+                    let bossHp = bossHp - extraDamage
+                    if bossHp <= 0 then newBest <- min newBest spent
+                    else newBest <- min newBest (play part false newBest spent (hp, mana, spells) (bossHp, bossDamage))
+                newBest
+        // boss's turn
+        else
+            let damage = max (bossDamage - armor) 1
+            let hp = hp - damage
+            if hp <= 0 then best else
+            play part true best spent (hp, mana, spells) (bossHp, bossDamage)
+
+    play part true 999999 0 (hp, mana, []) (bossHp, bossDamage)
+
+printfn "Part 1 - min to win: %d" (cheapestWin 1 (50, 500) (55, 8))
+printfn "Part 2 - min to win: %d" (cheapestWin 2 (50, 500) (55, 8))
diff --git a/day23.py b/day23.py
new file mode 100644
index 0000000..5e0763b
--- /dev/null
+++ b/day23.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+with open('day23.txt') as data:
+    program = [tuple(line.strip().replace(',', '').split()) for line in data]
+
+def execute_program(insts, a_reg = 0):
+    registers = {
+        'a': a_reg,
+        'b': 0,
+        'pc': 0
+    }
+
+    def exec_inst(inst):
+        match inst:
+            case 'hlf', reg:
+                registers[reg] /= 2
+                registers['pc'] += 1
+            case 'tpl', reg:
+                registers[reg] *= 3
+                registers['pc'] += 1
+            case 'inc', reg:
+                registers[reg] += 1
+                registers['pc'] += 1
+            case 'jmp', offset:
+                registers['pc'] += int(offset)
+            case 'jie', reg, offset:
+                if registers[reg] % 2 == 0:
+                    registers['pc'] += int(offset)
+                else:
+                    registers['pc'] += 1
+            case 'jio', reg, offset:
+                if registers[reg] == 1:
+                    registers['pc'] += int(offset)
+                else:
+                    registers['pc'] += 1
+    while True:
+        exec_inst(insts[registers['pc']])
+        if registers['pc'] >= len(insts):
+            print(registers['b'])
+            break
+# part 1
+execute_program(program, 0)
+# part 2
+execute_program(program, 1)
diff --git a/day24.fsx b/day24.fsx
new file mode 100644
index 0000000..a328dd9
--- /dev/null
+++ b/day24.fsx
@@ -0,0 +1,19 @@
+open System
+
+let rec comb n l =
+    match n, l with
+    | 0, _ -> [[]]
+    | _, [] -> []
+    | k, (x::xs) -> List.map ((@) [x]) (comb (k-1) xs) @ comb k xs
+
+let minQE (nums: int list) grps =
+    [2..(nums.Length/grps - 1)]
+    |> List.map (fun n -> comb n nums) |> List.concat
+    |> List.filter (fun cmb -> cmb |> List.sum = (nums |> List.sum) / grps)
+    |> Seq.groupBy (fun cmb -> cmb.Length)
+    |> Seq.minBy (fun (len,_) -> len) |> snd
+    |> Seq.map (fun cmb -> cmb |> List.map int64 |> List.reduce (*)) |> Seq.min
+
+let nums = IO.File.ReadAllLines "day24.txt" |> Array.map Int32.Parse |> Array.toList
+minQE nums 3 |> printfn "3 Groups: %A"
+minQE nums 4 |> printfn "4 Groups: %A"
diff --git a/day25.py b/day25.py
new file mode 100644
index 0000000..aa1c81c
--- /dev/null
+++ b/day25.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+
+pos_row, pos_col = 1, 1
+target_row, target_col = 2978, 3083
+
+curr = 20151125
+
+for i in range(sum(range(target_row + target_col - 1)) + target_col - 1):
+    curr = (curr * 252533) % 33554393
+print(curr)
diff --git a/day3.py b/day3.py
new file mode 100644
index 0000000..8d5854e
--- /dev/null
+++ b/day3.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+from collections import defaultdict
+with open('day3.txt') as data:
+    directions = data.read().strip()
+# part 1
+x, y = 0, 0
+houses = defaultdict(int)
+houses[(x, y)] = 1
+for direction in directions:
+    match direction:
+        case '^':
+            y += 1
+        case 'v':
+            y -= 1
+        case '>':
+            x += 1
+        case '<':
+            x -= 1
+    houses[(x, y)] += 1
+
+print(len(houses))
+
+# part 2
+santaX, santaY, roboX, roboY = 0, 0, 0, 0
+houses = defaultdict(int)
+houses[(0, 0)] = 2
+isSanta = True
+for direction in directions:
+    if isSanta:
+        match direction:
+            case '^':
+                santaY += 1
+            case 'v':
+                santaY -= 1
+            case '>':
+                santaX += 1
+            case '<':
+                santaX -= 1
+        houses[(santaX, santaY)] += 1
+    else:
+        match direction:
+            case '^':
+                roboY += 1
+            case 'v':
+                roboY -= 1
+            case '>':
+                roboX += 1
+            case '<':
+                roboX -= 1
+        houses[(roboX, roboY)] += 1
+    isSanta = not isSanta
+print(len(houses))
diff --git a/day4.py b/day4.py
new file mode 100644
index 0000000..ab2bb88
--- /dev/null
+++ b/day4.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+from hashlib import md5
+
+secret_key = r'iwrupvqb'
+
+num = 1
+while True:
+    hashed = md5(str(secret_key + str(num)).encode('utf-8')).hexdigest()
+    if hashed[:5] == '00000':
+        print(num)
+        break
+    num += 1
+
+num = 1
+while True:
+    hashed = md5(str(secret_key + str(num)).encode('utf-8')).hexdigest()
+    if hashed[:6] == '000000':
+        print(num)
+        break
+    num += 1
diff --git a/day5.py b/day5.py
new file mode 100644
index 0000000..25c6bc4
--- /dev/null
+++ b/day5.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+from collections import Counter
+from itertools import product
+
+with open('day5.txt') as data:
+    strings = [line.strip() for line in data]
+
+def isNice(string):
+    count_letters = Counter(string)
+    if sum(count_letters[x] for x in 'aeiou') < 3:
+        return False
+    if any(map(lambda x: x in string, ['ab', 'cd', 'pq', 'xy'])):
+        return False
+    if not any(map(lambda x: x*2 in string, 'abcdefghijklmnopqrstuvwxyz')):
+        return False
+    return True
+
+nice_counts = Counter(map(isNice, strings))
+print(nice_counts[True])
+
+def isNice2(string):
+    def windows(size):
+        for i in range(len(string) - size + 1):
+            yield string[i:i+size]
+    if not any(window[0] == window[2] for window in windows(3)):
+        return False
+    pairs = map(lambda x: x[0] + x[1], product('abcdefghijklmnopqrstuvwxyz', repeat=2))
+    if not any(map(lambda x: string.count(x) >= 2, pairs)):
+        return False
+    return True
+
+nice2_counts = Counter(map(isNice2, strings))
+print(nice2_counts[True])
diff --git a/day6.py b/day6.py
new file mode 100644
index 0000000..86c4abc
--- /dev/null
+++ b/day6.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+import numpy as np
+import re
+
+num_parse = re.compile(r'(toggle|off|on) (\d+,\d+) through (\d+,\d+)$')
+
+with open('day6.txt') as data:
+    instructions = [num_parse.search(line).groups((1, 2, 3)) for line in data]
+
+lights = np.zeros((1000, 1000), dtype=bool)
+for inst in instructions:
+    x1, y1 = map(int, inst[1].split(','))
+    x2, y2 = map(int, inst[2].split(','))
+    match inst[0]:
+        case 'toggle':
+            lights[x1:x2+1, y1:y2+1] = np.logical_not(lights[x1:x2+1, y1:y2+1])
+        case 'on':
+            lights[x1:x2+1, y1:y2+1] = True
+        case 'off':
+            lights[x1:x2+1, y1:y2+1] = False
+
+print(np.count_nonzero(lights))
+
+lights = np.zeros((1000, 1000), dtype=int)
+for inst in instructions:
+    x1, y1 = map(int, inst[1].split(','))
+    x2, y2 = map(int, inst[2].split(','))
+    match inst[0]:
+        case 'toggle':
+            lights[x1:x2+1, y1:y2+1] += 2
+        case 'on':
+            lights[x1:x2+1, y1:y2+1] += 1
+        case 'off':
+            region = lights[x1:x2+1, y1:y2+1]
+            region -= 1
+            region[region < 0] = 0
+
+print(np.sum(lights))
diff --git a/day7.py b/day7.py
new file mode 100644
index 0000000..3c7f0c3
--- /dev/null
+++ b/day7.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+from functools import cache
+from collections import defaultdict
+import operator
+
+ops = {
+    'EQ': lambda x: x,
+    'NOT': lambda x: ~x & 0xffff,
+    'AND': operator.iand,
+    'OR': operator.ior,
+    'RSHIFT': operator.rshift,
+    'LSHIFT': operator.lshift
+}
+
+wires = defaultdict(list)
+with open('day7.txt') as data:
+    for line in data:
+        calc, target = line.strip().split(' -> ')
+        calc = calc.split()
+        if len(calc) == 1:
+            wires[target] = ('EQ', calc[0])
+        elif len(calc) == 2:
+            wires[target] = (calc[0], calc[1])
+        else:
+            wires[target] = (calc[1], calc[0], calc[2])
+
+@cache
+def get_value(key):
+    try:
+        return int(key)
+    except ValueError:
+        pass
+
+    op, *value = wires[key]
+
+    if len(value) == 1:
+        return ops[op](get_value(value[0]))
+    else:
+        return ops[op](get_value(value[0]), get_value(value[1]))
+
+# part 1
+print(get_value('a'))
+
+# part 2
+get_value.cache_clear()
+
+wires['b'] = ('EQ', 3176)
+print(get_value('a'))
diff --git a/day8.py b/day8.py
new file mode 100644
index 0000000..7533ea4
--- /dev/null
+++ b/day8.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+import re
+
+with open('day8.txt') as data:
+    strings = [line.strip() for line in data]
+
+# part 1
+total = 0
+for string in strings:
+    orig_len = len(string)
+    eval_len = len(eval(string))
+    total += orig_len - eval_len
+
+print(total)
+
+# part 2
+def encode(s):
+    result = ''
+    for c in s:
+        if c == '"':
+            result += '\\\"'
+        elif c == '\\':
+            result += '\\\\'
+        else:
+            result += c
+    return '"' + result + '"'
+
+total = 0
+for string in strings:
+    encoded = encode(string)
+    total += len(encoded) - len(string)
+
+print(total)
diff --git a/day9.py b/day9.py
new file mode 100644
index 0000000..4ce3f69
--- /dev/null
+++ b/day9.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+from collections import defaultdict
+from itertools import permutations
+
+graph = defaultdict(lambda: defaultdict(int))
+with open('day9.txt') as data:
+    for line in data:
+        pos1, _, pos2, _, dist = line.strip().split()
+        graph[pos1][pos2] = int(dist)
+        graph[pos2][pos1] = int(dist)
+
+routes = list(permutations(graph.keys()))
+
+def route_dist(route):
+    dist = 0
+    for i in range(len(route)-1):
+        dist += graph[route[i]][route[i+1]]
+    return dist
+
+minDist, maxDist = float('inf'), 0
+for route in routes:
+    dist = route_dist(route)
+    if dist < minDist:
+        minDist = dist
+    if dist > maxDist:
+        maxDist = dist
+print(minDist)
+print(maxDist)