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-e7085453864431ace3ad8f3123b259ed0829ae74.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)
committer Kartik K. Agaram <vc@akkartik.com> 2015-07-08 14:47:12 -0700 1724 - first stab at printing interactive results' href='/akkartik/mu/commit/081run_interactive.cc?h=main&id=9fdda88b2ef9306128e0cbf6027f5a839ae3210c'>9fdda88b ^
286ca5a4 ^
9fdda88b ^



76d22198 ^



75a00270 ^


76d22198 ^

286ca5a4 ^
76d22198 ^




5f98a10c ^
0685569c ^
5f98a10c ^
75a00270 ^

0685569c ^
286ca5a4 ^
0685569c ^
5f98a10c ^
0685569c ^



75a00270 ^
0685569c ^
957ca9c9 ^










9fdda88b ^
dd9eda26 ^
90938fcb ^
dd9eda26 ^
9fdda88b ^
dd9eda26 ^

90938fcb ^
9fdda88b ^
dd9eda26 ^
9fdda88b ^

76d22198 ^

48e40252 ^
cfb142b9 ^

5f98a10c ^
cfb142b9 ^

4082acd2 ^
cfb142b9 ^

76d22198 ^
de92036d ^
76d22198 ^



d6c9fe63 ^
4082acd2 ^
9fdda88b ^

dd9eda26 ^
9fdda88b ^
f2564285 ^
9fdda88b ^
be13beeb ^

be13beeb ^




957ca9c9 ^
be13beeb ^
be13beeb ^

be13beeb ^


b17c196d ^
548cd00a ^

4082acd2 ^
de92036d ^
548cd00a ^

5f98a10c ^



e00d4854 ^
5f98a10c ^








e00d4854 ^
0685569c ^
0685569c ^

e00d4854 ^
0685569c ^


0cfc678f ^
e00d4854 ^
0cfc678f ^










0685569c ^
f0eb3556 ^

5f98a10c ^
06584c52 ^
f0eb3556 ^



166e3c0d ^
f0eb3556 ^
166e3c0d ^
5f98a10c ^
e4630643 ^

7afe09fb ^

e4630643 ^

166e3c0d ^



deba7494 ^




0a2026a6 ^
06584c52 ^


0a2026a6 ^
cc835be2 ^


f0eb3556 ^
4efc0ff3 ^
06584c52 ^
cfb142b9 ^
5f98a10c ^
06584c52 ^
f0eb3556 ^

06584c52 ^
5f98a10c ^
06584c52 ^








1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452