change directory names

This commit is contained in:
Pim Kunis 2023-12-04 10:25:33 +01:00
parent 3cff0fc5cf
commit 78b8881016
90 changed files with 0 additions and 0 deletions

4
22/.formatter.exs Normal file
View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

2
22/Makefile Normal file
View file

@ -0,0 +1,2 @@
day%:
mix run -e "AOC.Day$*.solve()"

21
22/README.md Normal file
View file

@ -0,0 +1,21 @@
# Aoc2022
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `aoc2022` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:aoc2022, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/aoc2022>.

41
22/day10.py Executable file
View file

@ -0,0 +1,41 @@
#!/bin/python3
program = list()
with open('day10.txt', 'r') as f:
for line in f:
line = line.strip()
if line == "noop":
program.append("noop")
else:
[cmd, arg] = line.split(" ")
program.append((cmd, int(arg)))
states = list()
state = 1
for instruction in program:
if instruction == 'noop':
states.append(state)
else:
(cmd, arg) = instruction
states.append(state)
states.append(state)
state += arg
part1 = 0
for i in range(19, len(states), 40):
part1 += (i+1) * states[i]
print(part1)
for y in range(6):
for x in range(40):
index = y * 40 + x
state = states[index]
if x == state or x == state-1 or x == state+1:
print('#', end='')
else:
print('.', end='')
print('')

139
22/day10.txt Normal file
View file

@ -0,0 +1,139 @@
noop
addx 12
addx -5
addx -1
noop
addx 4
noop
addx 1
addx 4
noop
addx 13
addx -8
noop
addx -19
addx 24
addx 1
noop
addx 4
noop
addx 1
addx 5
addx -1
addx -37
addx 16
addx -13
addx 18
addx -11
addx 2
addx 23
noop
addx -18
addx 9
addx -8
addx 2
addx 5
addx 2
addx -21
addx 26
noop
addx -15
addx 20
noop
addx 3
noop
addx -38
addx 3
noop
addx 26
addx -4
addx -19
addx 3
addx 1
addx 5
addx 3
noop
addx 2
addx 3
noop
addx 2
noop
noop
noop
noop
addx 5
noop
noop
noop
addx 3
noop
addx -30
addx -4
addx 1
addx 18
addx -8
addx -4
addx 2
noop
addx 7
noop
noop
noop
noop
addx 5
noop
noop
addx 5
addx -2
addx -20
addx 27
addx -20
addx 25
addx -2
addx -35
noop
noop
addx 4
addx 3
addx -2
addx 5
addx 2
addx -11
addx 1
addx 13
addx 2
addx 5
addx 6
addx -1
addx -2
noop
addx 7
addx -2
addx 6
addx 1
addx -21
addx 22
addx -38
addx 5
addx 3
addx -1
noop
noop
addx 5
addx 1
addx 4
addx 3
addx -2
addx 2
noop
addx 7
addx -1
addx 2
addx 4
addx -10
addx -19
addx 35
addx -1
noop
noop
noop

146
22/day10test.txt Normal file
View file

@ -0,0 +1,146 @@
addx 15
addx -11
addx 6
addx -3
addx 5
addx -1
addx -8
addx 13
addx 4
noop
addx -1
addx 5
addx -1
addx 5
addx -1
addx 5
addx -1
addx 5
addx -1
addx -35
addx 1
addx 24
addx -19
addx 1
addx 16
addx -11
noop
noop
addx 21
addx -15
noop
noop
addx -3
addx 9
addx 1
addx -3
addx 8
addx 1
addx 5
noop
noop
noop
noop
noop
addx -36
noop
addx 1
addx 7
noop
noop
noop
addx 2
addx 6
noop
noop
noop
noop
noop
addx 1
noop
noop
addx 7
addx 1
noop
addx -13
addx 13
addx 7
noop
addx 1
addx -33
noop
noop
noop
addx 2
noop
noop
noop
addx 8
noop
addx -1
addx 2
addx 1
noop
addx 17
addx -9
addx 1
addx 1
addx -3
addx 11
noop
noop
addx 1
noop
addx 1
noop
noop
addx -13
addx -19
addx 1
addx 3
addx 26
addx -30
addx 12
addx -1
addx 3
addx 1
noop
noop
noop
addx -9
addx 18
addx 1
addx 2
noop
noop
addx 9
noop
noop
noop
addx -1
addx 2
addx -37
addx 1
addx 3
noop
addx 15
addx -21
addx 22
addx -6
addx 1
noop
addx 2
addx 1
noop
addx -10
noop
noop
addx 20
addx 1
addx 2
addx 2
addx -6
addx -11
noop
noop
noop

104
22/day12.py Executable file
View file

@ -0,0 +1,104 @@
#!/bin/python3
import sys
sys.setrecursionlimit(1500)
hmap = list()
with open('day12.txt', 'r') as f:
for line in f:
hmap.append(line.strip())
h = len(hmap)
w = len(hmap[0])
for y in range(h):
for x in range(w):
if hmap[y][x] == 'S':
start = (x, y)
if hmap[y][x] == 'E':
end = (x, y)
def get_elevation(char):
if char == 'S':
return ord('a')
if char == 'E':
return ord('z')
return ord(char)
def get_adjacents(pos):
(x, y) = pos
return [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
def in_bounds(pos):
(x, y) = pos
return x >= 0 and x < w and y >= 0 and y < h
def get_char(hmap, pos):
(x, y) = pos
return hmap[y][x]
def valid_jump(hmap, pos, adj):
poschar = get_char(hmap, pos)
adjchar = get_char(hmap, adj)
poselev = get_elevation(poschar)
adjelev = get_elevation(adjchar)
return poselev + 1 >= adjelev
best_cost = {}
def find_path(hmap, path, pos):
if get_char(hmap, pos) == 'E':
return [path]
paths = list()
for adj in get_adjacents(pos):
if adj not in path and in_bounds(adj) and valid_jump(hmap, pos, adj):
if adj not in best_cost or best_cost[adj] > len(path):
best_cost[adj] = len(path)
new_path = set.copy(path)
new_path.add(adj)
paths.extend(find_path(hmap, new_path, adj))
return paths
shortest = None
paths = find_path(hmap, set([start]), start)
for path in paths:
if not shortest or len(path) < shortest:
shortest = len(path)
print('part1', shortest - 1)
trail_lengths = {}
def valid_jump2(hmap, pos, adj):
poschar = get_char(hmap, pos)
adjchar = get_char(hmap, adj)
poselev = get_elevation(poschar)
adjelev = get_elevation(adjchar)
return adjelev + 1 >= poselev
def find_trail(hmap, trail, pos):
for adj in get_adjacents(pos):
if adj not in trail and in_bounds(adj) and valid_jump2(hmap, pos, adj):
trail_length = len(trail) + 1
if adj not in trail_lengths or trail_lengths[adj] > trail_length:
trail_lengths[adj] = trail_length
new_trail = list.copy(trail)
new_trail.append(adj)
find_trail(hmap, new_trail, adj)
find_trail(hmap, [end], end)
shortest = None
for y in range(h):
for x in range(w):
if (x, y) in trail_lengths:
if get_elevation(get_char(hmap, (x, y))) == ord('a'):
trail_length = trail_lengths[(x, y)]
if not shortest or trail_length < shortest:
shortest = trail_length
print('part2', shortest - 1)

41
22/day12.txt Normal file
View file

@ -0,0 +1,41 @@
abacccaaaacccccccccccaaaaaacccccaaaaaaccccaaacccccccccccccccccccccccccccccccccccccccccccaaaaa
abaaccaaaacccccccccccaaaaaaccccccaaaaaaaaaaaaaccccccccccccccccccccccccccccccccccccccccccaaaaa
abaaccaaaacccccccccccaaaaacccccaaaaaaaaaaaaaaaccccccccccccccccccccccccccccccccccccccccccaaaaa
abccccccccccccccccccccaaaaacccaaaaaaaaaaaaaaaacccccccccccccccccccccccccccaaaccccccccccccaaaaa
abccccccccccccccccccccaacaacccaaaaaaaaccaaaaaccccccccccccccccccccccccccccaaaccccccccccccaccaa
abcccccccccccccaacccaaaccccccaaaaaaaaaccaaaaaccccccccccccccccccccccccccccccacccccccccccccccca
abcccccccccccaaaaaaccaaaccacccccaaaaaaacccccccccccccccccccccccccciiiicccccccddddddccccccccccc
abcccccccccccaaaaaaccaaaaaaaccccaaaaaacccccaacccccccaaaccccccccciiiiiiiicccdddddddddacaaccccc
abccccccccccccaaaaaaaaaaaaacccccaaaaaaacaaaacccccccaaaacccccccchhiiiiiiiiicddddddddddaaaccccc
abcccccccccccaaaaaaaaaaaaaacccccccaaacccaaaaaacccccaaaaccccccchhhipppppiiiijjjjjjjddddaaccccc
abcccccccccccaaaaaaaaaaaaaaccccccccccccccaaaaaccccccaaaccccccchhhpppppppiijjjjjjjjjddeeaccccc
abcccccccccccccccccaaaaaaaacccccccccccccaaaaaccccccccccccccccchhppppppppppjjqqqjjjjjeeeaacccc
abccccccccccccccccccaaaaaaaacccccccccccccccaacccccccccccccccchhhpppuuuupppqqqqqqqjjjeeeaacccc
abcccccccccccccccccccaacccacccccccccccccccccccccccccccccccccchhhopuuuuuuppqqqqqqqjjjeeecccccc
abacccccccccccccaaacaaaccccccccccccccccccccccccccccaaccccccchhhhoouuuuuuuqvvvvvqqqjkeeecccccc
abaccccccccccccaaaaaacccccaaccccccccccccccccccccccaaaccccccchhhooouuuxxxuvvvvvvqqqkkeeecccccc
abaccccccccccccaaaaaacccaaaaaaccccccccccccccccccaaaaaaaaccchhhhooouuxxxxuvyyyvvqqqkkeeecccccc
abcccccccccccccaaaaacccaaaaaaaccccccccccccccccccaaaaaaaaccjjhooooouuxxxxyyyyyvvqqqkkeeecccccc
abccccccccccccccaaaaaacaaaaaaaccccccccaaaccccccccaaaaaaccjjjooootuuuxxxxyyyyyvvqqkkkeeecccccc
abccccccccccccccaaaaaaaaaaaaacccccccccaaaacccccccaaaaaacjjjooootttuxxxxxyyyyvvrrrkkkeeecccccc
SbccccccccccccccccccaaaaaaaaacccccccccaaaacccccccaaaaaacjjjoootttxxxEzzzzyyvvvrrrkkkfffcccccc
abcccccccccccaaacccccaaaaaaacaaaccccccaaaccccccccaaccaacjjjoootttxxxxxyyyyyyvvvrrkkkfffcccccc
abcccccccccaaaaaacccaaaaaacccaaacacccaacccccccccccccccccjjjoootttxxxxyxyyyyyywvvrrkkkfffccccc
abcccccccccaaaaaacccaaaaaaaaaaaaaaaccaaacaaacccccaacccccjjjnnnttttxxxxyyyyyyywwwrrkkkfffccccc
abcaacacccccaaaaacccaaacaaaaaaaaaaaccaaaaaaacccccaacaaacjjjnnnntttttxxyywwwwwwwwrrrlkfffccccc
abcaaaaccccaaaaacccccccccaacaaaaaaccccaaaaaacccccaaaaacccjjjnnnnnttttwwywwwwwwwrrrrllfffccccc
abaaaaaccccaaaaaccccccaaaaaccaaaaacaaaaaaaaccccaaaaaaccccjjjjinnnntttwwwwwsssrrrrrllllffccccc
abaaaaaaccccccccccccccaaaaacaaaaaacaaaaaaaaacccaaaaaaacccciiiiinnnntswwwwssssrrrrrlllfffccccc
abacaaaaccccccccccccccaaaaaacaaccccaaaaaaaaaaccccaaaaaaccccciiiinnnssswwsssssllllllllfffccccc
abccaaccccccccccccccccaaaaaaccccccccccaaacaaaccccaaccaacccccciiiinnsssssssmmllllllllfffaacccc
abccccccccccccccccccccaaaaaaccccccccccaaaccccccccaaccccccccccciiinnmsssssmmmmlllllgggffaacccc
abcccccccccccccccaccccccaaacccccccccccaaccccccccccccccccccccccciiimmmsssmmmmmgggggggggaaacccc
abcccccccccaaaaaaaaccccccccccccccccccccccccccccaaaaaccccccccccciiimmmmmmmmmgggggggggaaacccccc
abccccccccccaaaaaaccccccccccccccccccaacccccccccaaaaacccccccccccciiimmmmmmmhhggggcaaaaaaaccccc
abccccccccccaaaaaacccccccccccccccccaacccccccccaaaaaacccccccccccciihhmmmmhhhhgccccccccaacccccc
abccccaacaaaaaaaaaaccccccccccccccccaaaccccccccaaaaaaccccccccccccchhhhhhhhhhhaaccccccccccccccc
abccccaaaaaaaaaaaaaaccccccccccaaccaaaaccccccccaaaaaacccaaacccccccchhhhhhhhaaaaccccccccccccccc
abcccaaaaaaaaaaaaaaaccccccccaaaaaacaaaacacaccccaaaccccaaaacccccccccchhhhccccaaccccccccccaaaca
abcccaaaaaacacaaacccccccccccaaaaaaaaaaaaaaacccccccccccaaaacccccccccccaaaccccccccccccccccaaaaa
abcccccaaaacccaaaccccccccccaaaaaaaaaaaaaaaaccccccccccccaaacccccccccccaaacccccccccccccccccaaaa
abcccccaacccccaacccccccccccaaaaaaaaaaaaaccccccccccccccccccccccccccccccccccccccccccccccccaaaaa

5
22/day12test.txt Normal file
View file

@ -0,0 +1,5 @@
Sabqponm
abcryxxl
accszExk
acctuvwj
abdefghi

89
22/day14.py Executable file
View file

@ -0,0 +1,89 @@
#!/bin/python3
from collections import defaultdict
from enum import Enum
from itertools import pairwise
class Tile(Enum):
AIR = 1
ROCK = 2
SOURCE = 3
SAND = 4
grid = defaultdict(lambda: Tile.AIR)
with open('inputs/day14.txt', 'r') as f:
for line in f:
for (start, end) in pairwise(line.strip().split(' -> ')):
[sx, sy] = map(int, start.split(','))
[ex, ey] = map(int, end.split(','))
dx = 0
dy = 0
if ex > sx:
dx = 1
elif ex < sx:
dx = -1
if ey > sy:
dy = 1
elif ey < sy:
dy = -1
curx = sx
cury = sy
grid[(curx, cury)] = Tile.ROCK
while curx != ex or cury != ey:
curx += dx
cury += dy
grid[(curx, cury)] = Tile.ROCK
highest_y = -1
for (x, y), tile in grid.items():
if y > highest_y:
highest_y = y
def query_grid(x, y):
if y == highest_y + 2:
return Tile.ROCK
return grid[(x, y)]
(sourcex, sourcey) = (500, 0)
def abyss_underneath(x, y):
for (gridx, gridy), tile in grid.items():
if gridy > y and gridx == x and tile != Tile.AIR:
return False
return True
# voided = False
blocked = False
while True:
(x, y) = (sourcex, sourcey)
while True:
# if abyss_underneath(x, y):
# voided = True
# break
if query_grid(x, y+1) == Tile.AIR:
y += 1
elif query_grid(x-1, y+1) == Tile.AIR:
y += 1
x -= 1
elif query_grid(x+1, y+1) == Tile.AIR:
y += 1
x += 1
else:
if (x, y) == (sourcex, sourcey):
blocked = True
break
# if voided:
# break
if blocked:
break
grid[(x, y)] = Tile.SAND
rest_count = 0
for (x, y), tile in grid.items():
if tile == Tile.SAND:
rest_count += 1
print(rest_count + 1)

101
22/day15.py Executable file
View file

@ -0,0 +1,101 @@
#!/bin/bash
# thanks github.com/bernadetten
import sys
import re
from itertools import pairwise
test = bool(int(sys.argv[1]))
file_path = ""
data = ""
prodata = []
given_y = [int(sys.argv[2])]
absent_points = []
part1 = bool(int(sys.argv[3]))
max_y = int(sys.argv[4])
if test:
file_path = "test_day_15.txt"
else:
file_path = "input_day_15.txt"
with open(file_path, "r") as f:
data = f.read().split("\n")
# The anhatten distance
def md(x1, y1, x2, y2):
return abs(x1 - x2) + abs(y1 - y2)
# Processing the data.
for line in data:
if line != "":
m = re.match(
r"Sensor at x=(?P<sx>-?\d+), y=(?P<sy>-?\d+): closest beacon is at x=(?P<bx>-?\d+), y=(?P<by>-?\d+)",
line.strip(),
)
x_s = int(m.group("sx"))
y_s = int(m.group("sy"))
x_b = int(m.group("bx"))
y_b = int(m.group("by"))
dis = md(x_s, y_s, x_b, y_b)
prodata += [((x_s, y_s), (x_b, y_b), dis)]
# All y-values we want to loop over if part 2
if not part1:
given_y = [x for x in range(0, max_y + 1)]
# Merge all ranges
def mergeranges(ranges):
super_range = ranges[0]
ranges = ranges[1:]
for (xl, xh) in ranges:
# print('super', super_range)
#print('comparing to', xl, ' ', xh)
if not (xl - 1 <= super_range[1] and xl >= super_range[0]):
return xl - 1
if xh > super_range[1]:
super_range = (super_range[0], xh)
return None
# Now we calculate the impossible ranges
for y in given_y:
if part1:
beacon_on_y = [b[0] for _, b, _ in prodata if b[1] == y]
absent_points = []
#print(y)
# We need to add the ranges of points that are not available.
for source, beacon, dis in prodata:
height_diff = abs(source[1] - y)
remain_dist = dis - height_diff
if remain_dist >= 0:
# print('Source ', source, ' y ', y, ' dis ', dis, ' diff ', height_diff)
lower_x = source[0] - remain_dist
higher_x = source[0] + remain_dist
if part1:
absent_points += [
x for x in range(lower_x, higher_x + 1) if x not in beacon_on_y
]
else:
absent_points += [(lower_x, higher_x)]
# If we are doing part two we need to post process the output per line.
if not part1:
# print(absent_points)
absent_points = sorted(absent_points)
#print(absent_points)
result = mergeranges(absent_points)
if result != None:
print('part2, x:', result, 'y:', y, 'output:', result *4000000 +y)
sys.exit()
if part1:
print('part1', len(set(absent_points)))

103
22/day16.py Executable file
View file

@ -0,0 +1,103 @@
#!/bin/python3
import re
from dijkstar import Graph, find_path
import numpy as np
from itertools import combinations
import itertools as it
from tqdm import tqdm
valves = list()
with open('inputs/day16.txt', 'r') as f:
for line in f:
m = re.match(r"Valve (?P<name>[A-Z]{2}) has flow rate=(?P<rate>\d+); tunnels? leads? to valves? (?P<tunnels>.*)", line.strip())
name = m.group("name")
rate = int(m.group("rate"))
tunnels = m.group("tunnels").split(", ")
valves.append({"name": name, "rate": rate, "tunnels": tunnels})
indices = {}
for i, valve in enumerate(valves):
indices[valve["name"]] = i
graph = Graph()
for v1 in valves:
for v2 in v1["tunnels"]:
graph.add_edge(v1["name"], v2, 1)
graph.add_edge(v2, v1["name"], 1)
adjs = np.zeros((len(valves), len(valves)))
for v1, v2 in combinations(map(lambda v: v["name"], valves), 2):
cost = find_path(graph, v1, v2).total_cost
adjs[indices[v1], indices[v2]] = cost
adjs[indices[v2], indices[v1]] = cost
class Valve:
def __init__(self, name, rate):
self.name = name
self.rate = rate
def value(self, start, time_left):
cost = adjs[indices[start], indices[self.name]] + 1
return (self.rate * (time_left - cost), cost) # one off?
valve_map = dict()
valve_set = set()
for valve in valves:
if valve["rate"] > 0:
valve_set.add(valve["name"])
valve_map[valve["name"]] = Valve(valve["name"], valve["rate"])
def search(valve_set, current, time_left, visited):
to_visit_set = valve_set.difference(visited)
best_score = 0
for to_visit in to_visit_set:
# print("time left:", time_left)
# print("current:", current)
# print("going to visit:", to_visit)
visited_copy = visited.copy()
visited_copy.add(to_visit)
score, dtime = valve_map[to_visit].value(current, time_left)
# print("+score:", score, "+time", dtime)
if time_left - dtime >= 0:
score = score + search(valve_set, to_visit, time_left - dtime, visited_copy)
if not best_score or score > best_score:
best_score = score
return best_score
# print('part1', search(valve_set, "AA", 30, set()))
# part2 idea
# BIGBRAIN
# divide valve set in two disjoint sets
# apply same search algorithm to both sets.
# sum the scores of the sets to get total score
# brute force set distribution
def partition(pred, iterable):
t1, t2 = it.tee(iterable)
return it.filterfalse(pred, t1), filter(pred, t2)
def partitions(l):
return list(filter(lambda x: [] not in x, [[[x[1] for x in f] for f in partition(lambda x: x[0], zip(pattern, l))] for pattern in it.product([True, False], repeat=len(l))]))
# for [lhs, rhs] in parts:
# if (set(lhs), set(rhs)) not in result and (set(rhs), set(lhs)) not in result:
# result.append((set(lhs), set(rhs)))
# return result
best_score = 0
for part in tqdm(partitions(list(valve_set))):
[part1, part2] = part
part1 = set(part1)
score1 = search(part1, 'AA', 26, set())
part2 = set(part2)
score2 = search(part2, 'AA', 26, set())
grand_score = score1 + score2
if grand_score > best_score:
best_score = grand_score
print('part2', best_score)

311
22/day17.py Executable file
View file

@ -0,0 +1,311 @@
#!/bin/python3
from enum import Enum
from collections import defaultdict
from itertools import pairwise, combinations
class Move(Enum):
LEFT = 1
RIGHT = 2
DOWN = 3
moves = list()
with open('inputs/day17.txt', 'r') as f:
for line in f:
for char in line.strip():
if char == '<':
moves.append(Move.LEFT)
elif char == '>':
moves.append(Move.RIGHT)
def move_stream():
while True:
for i, d in enumerate(moves):
yield (i, d)
yield (i, Move.DOWN)
move_map = {Move.LEFT: (-1, 0), Move.RIGHT: (1, 0), Move.DOWN: (0, -1)}
class Rock():
def __init__(self, shape):
self.blocks = shape
def init_position(self, y_top):
self.blocks = list(map(lambda pos: (pos[0] + 2, pos[1] + y_top + 4), self.blocks))
def move(self, chamber, move):
new_blocks = self.move_blocks(move)
if self.check_valid(chamber, new_blocks):
self.blocks = new_blocks
return True
else:
return False
def move_blocks(self, move):
(movex, movey) = move_map[move]
return list(map(lambda block: (block[0] + movex, block[1] + movey), self.blocks))
def check_valid(self, chamber, blocks):
for (x, y) in blocks:
if x > 6 or x < 0 or chamber[y][x]:
return False
return True
shapes = [
[(0, 0), (1, 0), (2, 0), (3, 0)],
[(0, 1), (1, 1), (2, 1), (1, 0), (1, 2)],
[(0, 0), (1, 0), (2, 0), (2, 1), (2, 2)],
[(0, 0), (0, 1), (0, 2), (0, 3)],
[(0, 0), (1, 0), (0, 1), (1, 1)]
]
def rock_stream():
while True:
for i, shape in enumerate(shapes):
yield (i, Rock(shape.copy()))
chamber = defaultdict(lambda: defaultdict(lambda: False))
for i in range(7):
chamber[-1][i] = True
def print_chamber(chamber, rock, print_index):
keys = list(reversed(sorted(chamber.keys())))
for key in keys:
if print_index:
print(f'{key}\t', end='')
for i in range(7):
if chamber[key][i]:
print('#', end='')
elif rock and (i, key) in rock.blocks:
print('@', end='')
else:
print('.', end='')
print('')
def prune_chamber(chamber):
ys = list(reversed(sorted(chamber.keys())))
prune_height = None
for y_high, y_low in pairwise(ys):
prunable = True
for x in range(7):
if not chamber[y_high][x] and not chamber[y_low][x]:
prunable = False
break
if prunable:
prune_height = y_low
break
if prune_height:
for y in ys:
if y < prune_height:
del chamber[y]
return prune_height != None
def row_to_mask(row):
mask = 0
bit = 1
for x in range(6, -1, -1):
if row[x]:
mask = mask | bit
bit = bit << 1
return mask
barrier_fingerprints = defaultdict(lambda: list())
rock_count = 20220
prune_freq = 100
y_top = -1
moves_gen = move_stream()
rocks_gen = rock_stream()
rocks_fallen = 0
rock_idx = None
for rock_idx, rock in rocks_gen:
rock.init_position(y_top)
for move_idx, move in moves_gen:
moved = rock.move(chamber, move)
if not moved and move == Move.DOWN:
for (x, y) in rock.blocks:
if y > y_top:
y_top = y
chamber[y][x] = True
rocks_fallen += 1
break
ys = set(map(lambda x: x[1], rock.blocks))
for y in ys:
all_filled = True
for x in range(7):
if not chamber[y][x] and not chamber[y-1][x]:
all_filled = False
break
if all_filled:
mask_high = row_to_mask(chamber[y])
mask_low = row_to_mask(chamber[y-1])
fingerprint = (rock_idx, move_idx, mask_high, mask_low)
barrier_fingerprints[fingerprint].append((rocks_fallen, y))
if rocks_fallen == rock_count:
break
print('part1', y_top + 1)
candidate_fp = None
candidate_ys_count = 0
for fp, ys in barrier_fingerprints.items():
l = len(ys)
if l > candidate_ys_count:
candidate_ys_count = l
candidate_fp = fp
# print('candidate FP:', candidate_fp)
first_occ = barrier_fingerprints[candidate_fp][0]
second_occ = barrier_fingerprints[candidate_fp][1]
height_cycle = second_occ[1] - first_occ[1]
rock_cycle = second_occ[0] - first_occ[0]
# print('first occurance:', 'height', first_occ[1], 'fallen', first_occ[0])
# print('second occurance:', 'height', second_occ[1], 'fallen', second_occ[0])
# print('height cycle:', second_occ[1] - first_occ[1])
# print('rock cycle:', second_occ[0] - first_occ[0])
rocks_left = 1000000000000 - first_occ[0]
superheight = first_occ[1]
superheight += (rocks_left // rock_cycle) * height_cycle
rocks_left %= rock_cycle
print(rocks_left, superheight)
# We just need $rocks_left rocks more!
# Move to the next cycle point
# Then simulate $rocks_left movements to get the height this generates.
# Add this to superheight then subtract one height_cycle.
for rock_idx, rock in rocks_gen:
rock.init_position(y_top)
for move_idx, move in moves_gen:
moved = rock.move(chamber, move)
if not moved and move == Move.DOWN:
for (x, y) in rock.blocks:
if y > y_top:
y_top = y
chamber[y][x] = True
rocks_fallen += 1
break
ys = set(map(lambda x: x[1], rock.blocks))
cycle_start = False
for y in ys:
all_filled = True
for x in range(7):
if not chamber[y][x] and not chamber[y-1][x]:
all_filled = False
break
if all_filled:
mask_high = row_to_mask(chamber[y])
mask_low = row_to_mask(chamber[y-1])
fingerprint = (rock_idx, move_idx, mask_high, mask_low)
if fingerprint == candidate_fp:
cycle_start = True
break
if cycle_start:
break
start_height = y_top
for rock_idx, rock in rocks_gen:
rock.init_position(y_top)
for move_idx, move in moves_gen:
moved = rock.move(chamber, move)
if not moved and move == Move.DOWN:
for (x, y) in rock.blocks:
if y > y_top:
y_top = y
chamber[y][x] = True
rocks_left -= 1
break
if rocks_left == 0:
break
end_height = y_top
superheight += end_height - start_height
print('part2', superheight + 3)
# for fp, ys in barrier_fingerprints.items():
# print('fingerprint:', fp)
# print('count:', len(ys))
# NEW IDEA
# Find a cycle.
# A cycle can be characterized by: rock rotation, direction rotation and rock formation.
# Problem is, we need to save a lot of the rock formation, because new rocks can fall down a long while.
# Additionally, we cannot just save the height of each column, because rocks can move underneath hanging arches.
# Possible solution: find lines that block this.
# Possibly in one line, definitely in two lines.
# IDEA:
# Create bitmap.
# Easily compare bitmaps with eachother to find cycle
# def print_mask(mask):
# bit = 1 << 6
# for i in range(7):
# if mask & (bit >> i):
# print('1', end='')
# else:
# print('0', end='')
# print('')
# def print_bitmap(bitmap):
# for row in bitmap:
# print_mask(row)
# bitmap = []
# ys = list(reversed(sorted(chamber.keys())))
# y_min = min(ys)
# y_max = max(ys)
# for y in range(y_min + 1, y_max + 1):
# mask = 0
# bit = 1
# for x in range(6, -1, -1):
# if chamber[y][x]:
# mask = mask | bit
# bit = bit << 1
# print_mask(mask)
# bitmap.append(mask)
# def in_order(l):
# last = None
# for x in l:
# if last == None or x > last:
# last = x
# else:
# return False
# return True
# def same_distance(l):
# [y1, y2, y3] = l
# return y2 - y1 == y3 - y2
# def is_cycle(bitmap, l):
# [y1, y2, y3] = l
# distance = y3 - y2
# for i in range(distance + 1):
# if bitmap[y1+i] != bitmap[y2]+i:
# return False
# return True
# Now try to find the cycle!
# We loop over every row.
# We find all indices of this row in the whole chamber
# checked = []
# print('finding cycle')
# for row in bitmap:
# print('row:', row)
# if row not in checked:
# checked.append(row)
# indices = [i for i, x in enumerate(bitmap) if x == row]
# if len(indices) >= 3:
# for maybe_cycle in combinations(indices, 3):
# if in_order(maybe_cycle) and same_distance(maybe_cycle):
# if is_cycle(bitmap, maybe_cycle):
# print('FOUND CYCLE:', maybe_cycle)
# # print_bitmap(bitmap)

113
22/day18.py Executable file
View file

@ -0,0 +1,113 @@
#!/bin/python3
from collections import defaultdict
from queue import Queue
cubes = set()
with open('inputs/day18.txt', 'r') as f:
for line in f:
[x, y, z] = line.split(',')
cubes.add((int(x), int(y), int(z)))
def ordered(pos1, pos2):
if pos1 > pos2:
return (pos1, pos2)
else:
return (pos2, pos1)
sides = defaultdict(lambda: 0)
for cube in cubes:
(x, y, z) = cube
cube_sides = []
cube_sides.append(ordered(cube, (x+1, y, z)))
cube_sides.append(ordered(cube, (x-1, y, z)))
cube_sides.append(ordered(cube, (x, y+1, z)))
cube_sides.append(ordered(cube, (x, y-1, z)))
cube_sides.append(ordered(cube, (x, y, z+1)))
cube_sides.append(ordered(cube, (x, y, z-1)))
for side in cube_sides:
sides[side] += 1
outer_sides = set()
for side, count in sides.items():
if count == 1:
outer_sides.add(side)
print("part1", len(outer_sides))
# Determine max and min coords
x_min = min(map(lambda a: a[0], cubes))
x_max = max(map(lambda a: a[0], cubes))
y_min = min(map(lambda a: a[1], cubes))
y_max = max(map(lambda a: a[1], cubes))
z_min = min(map(lambda a: a[2], cubes))
z_max = max(map(lambda a: a[2], cubes))
# Increase bounds by 1 in all directions
x_min -= 1
x_max += 1
y_min -= 1
y_max += 1
z_min -= 1
z_max += 1
# Flood fill from 1 corner to find all reachable cubes.
class SetQueue(Queue):
def _init(self, maxsize):
self.queue = set()
def _put(self, item):
self.queue.add(item)
def _get(self):
return self.queue.pop()
cube_queue = SetQueue()
cube_queue.put((x_min, y_min, z_min))
reachable_cubes = set()
max_cubes = (x_max - x_min) * (y_max - y_min) * (z_max - z_min)
while True:
if cube_queue.empty():
break
cube = cube_queue.get()
(x, y, z) = cube
reachable_cubes.add(cube)
neighbors = []
neighbors.append((x+1, y, z))
neighbors.append((x-1, y, z))
neighbors.append((x, y+1, z))
neighbors.append((x, y-1, z))
neighbors.append((x, y, z+1))
neighbors.append((x, y, z-1))
for neighbor in neighbors:
(nx, ny, nz) = neighbor
if nx < x_min or nx > x_max or ny < y_min or ny > y_max or nz < z_min or nz > z_max:
continue
if neighbor in reachable_cubes or neighbor in cubes:
continue
# Found a new empty cube!
cube_queue.put(neighbor)
# Okay, found all empty cube outside the droplet.
surface_sides = set()
for empty in reachable_cubes:
(x, y, z) = empty
cube_sides = []
cube_sides.append(ordered(empty, (x+1, y, z)))
cube_sides.append(ordered(empty, (x-1, y, z)))
cube_sides.append(ordered(empty, (x, y+1, z)))
cube_sides.append(ordered(empty, (x, y-1, z)))
cube_sides.append(ordered(empty, (x, y, z+1)))
cube_sides.append(ordered(empty, (x, y, z-1)))
for side in cube_sides:
if side in outer_sides:
surface_sides.add(side)
print("part2", len(surface_sides))

54
22/day8.py Executable file
View file

@ -0,0 +1,54 @@
#!/bin/python3
forest = []
with open('day8.txt', 'r') as f:
for line in f:
forest.append(list(map(int, list(line.strip()))))
height = len(forest)
width = len(forest[0])
heighest = -1
heighest_x = -1
heighest_y = -1
for y in range(height):
for x in range(width):
h = forest[x][y]
tree_count = 1
tree_count_cur = 0
for cur_x in range(x+1, width):
tree_count_cur += 1
if forest[cur_x][y] >= h:
break
tree_count *= tree_count_cur
tree_count_cur = 0
for cur_x in range(x-1, -1, -1):
tree_count_cur += 1
if forest[cur_x][y] >= h:
break
tree_count *= tree_count_cur
tree_count_cur = 0
for cur_y in range(y+1, height):
tree_count_cur += 1
if forest[x][cur_y] >= h:
break
tree_count *= tree_count_cur
tree_count_cur = 0
for cur_y in range(y-1, -1, -1):
tree_count_cur += 1
if forest[x][cur_y] >= h:
break
tree_count *= tree_count_cur
if tree_count > heighest:
heighest = tree_count
heighest_x = x
heighest_y = y
print(x, y, heighest)

99
22/day8.txt Normal file
View file

@ -0,0 +1,99 @@
113003322412033102023303501444545044215232525401341546163452453404402234201151432242402140110220101
332001304022012142421445502213221330453061535265122314201352335233021055055200345412200440033322200
011113132214324432134325045311145402450516101640412524056254134552334050434552541351100000142223210
120100233011423423144030334144351644642605521663331053414601341464201040045104150001214204243212330
032313022420343002352401031112122630544621450306410014210122655033613410314450242104133332033433110
330133431221024552500405304311611205251454505525065601244143364343441153320511500334525320443331122
232021314224321045040222145130205324423110304256225426345644345334310200416004113133521143332200422
312322022143422250013255366221252062135020112327134227764002156152041134323314242255102200014240004
212411010402324310541162616440606266620317315234166112525535542054554446314636025435501101142314410
130420010020500305012262532345565125253527765422533261626735742433241362062404452514044512024131310
241211034142224054050344306633445512263575724527746635663111434536510100261040501431453540340134211
241313213114241230212646636605641533121654131662127125143714342377771455431346140255405303533314030
200214302143123545226333105604234323144236661442633717414531213335635263423430225510502553055320124
432210433113545424321340236152364457166745737761176472274142327714574465315263302246430530531052340
044321111115013046066155407144655275272121513224325736736453741437462322257332111603344504052313220
423214232334431334032453034121465736632235428586864276787383246667147623517713521254056250023050122
341124205512002021001520535463145337475482376656634323542842654415315313467514115356311404235042224
132001402100660310301501776141144746672773424257875288227677277663442356545463514004411150412531212
001052001350350313612735275534436625275423778573334245773528222523563225127612470242660400232440014
000112513501320042013743116251356437388536835723273464367352632733656224242323651012010230151310024
432343513243516355144362617334462875826626872624526547773732534284688857151541627654520220114445210
030155000415201011165562424113226826748747748853864385358746645352362468546644524342115335322112153
521354124325146425431532157187775456325686557739354768338786565328828884835715454276200630310531024
453541003326165663116714725533384334462564846638857736639973473868246353547315725644665433532024310
550412430051564655211263423273445384473677893666497965987545884578247877687261326676604334421513240
413054444206603631433161684337566624388758573363447988796769799695582635658423643315241325425634024
215150330040102563666211553675525355868889739969438648979533595459347577832874756321426443250541325
421424650534663421741533522275548697677937396576933765698789956783468276533784535453223333266121254
422144151564541614312463257866683347483647399747895987469373443743399762878755625665635533442122100
142126413036631514413457664863683937946499766465555784756389698779343558673226534437441452602242233
305201320636671251145832252388563746733774564949748445664654449965453936743822654651524764154313443
011300622102111771164477857675385967936859964787497949987577437799784855267278677674646432241223234
340321113267666756754785648366853336456946997569954774746449465943647534953257556352535151305025463
541411012547643576586448655984493857478575997945994447954587596973768677885376565465523461502535643
444321100654632152854364424757747839747855469574958798776977549543479354379242483435267721134026240
020626205154575316847673866764644374748789977457499575788744688854499953995222366237635424744520350
335506510045711628565868794334457797677874456886875779795697459467453749364727248672726163736041204
500111603536263112863778287468558998649869948858895979757648556554566675849574438477347434571054505
122000100243546685578334388934875844674864976796788677978555975645687865954363424837536471770452415
141414215752566148738527647673948945657599775586897776875597686768689334698832463748744764733053124
100340653262173232623746876388947885855898958859977856795865855466954797947557362275815142426540331
466303032723142355554844869739456445889568997967558595977567767954454556686589744843263767635404425
004613266772514623473428884775998765955988667675865595697667595999549844548767382824562175546612214
223062321176322437826876799485789855749797897795766857786556556466469876653886288833557467773301562
413332261232125275243773533956777794896856665777776697967896558687464977634963578724434267752313063
552404621241274462672869963657657685959857786696669988675856559577744587934439473355436162566631240
435165353647675322378767844955594694568958979668688889986755769858696479453833942343873533447232052
244432367557727222376845574654778567657966566799677789797957677969848457379553465846862364131450331
500160365677454526747395979487899988575755756767767688698968968964795455975647975338665645777505164
102431235675118652565457756594548744578555576979976998997755878856465958937486343448585657465615526
246165542416756587676444776898888956766578897997878779867867799995588485976853457557557151277215635
001643325345324464653748985776747945569696578767779897778688898756644945899654857626437342132540232
012101664761176266736346897839595848575598988867686699987969655574844656549763626542283366457311330
561504075335422826822483989945889874569866596668697889889787698578564685356683642368537236162426203
352203514134651772748288733934776699589795768989986896977876655856647995457449532883535245357460413
033642404423741338448747543456567587875878659898696769778857567974989696766857638532623516361132200
616444206117715325587799743549596679896878996579778797758659755584898985549986338256726477267555103
520455143625244274365846334377848464679657799659996799959789655886895549885388585227864441542440633
301346217675555884462648646383444656479799855995796958569776779656497648598636775768426556776465212
520404231326621664428744458384498945886755796967559979558788575647874897993564426835362551773515204
316242511662326327222423774658467889697656957595795766869586965778777639684376585246752671616104436
415146426112762185773325386754349955694758878879858887885879799994465943455896555866711732413133541
355625411617227324663526785865944884474696755859788678978799585984575877863878686426651623174052622
250022125746424524625262754995697689898769757895898887999798967686567449959582647242127737515510342
525264040416627662865878845844435466984695749789657699584976867495646849545456285287242646554654353
036123646424431555263267827839763498876769495677697448449595455554764947373454763645727333510646146
204566364235641316654753765445963394466448895664985649465498769864394998376778764453573662441355520
220335031661255773663868585338574847865979585777445874888879656858779783486746544464161776603563641
222513656331342447187422756294679343838685457555849784967456454674489536467765228571211235451313300
420041535613636672654268683685394696895377789594449696975655499485443388622338663537334646443120355
525056224212611227113776876877874646779895859895448998785578796984395868862632743546145340000633644
404314430546135313471247434875543567783736878545548779967377554684776373253726223752325712616213051
353422360566111142127744587245868774578548754579967945935667489466386244448265471714226610251556141
240135510612042471674334266253242658953336636546539684393847584358732442564537232411555541300234143
555204024234655515726125842362866243634643845579964798949797638778425653264527614711662106606433551
203524350135524444622315434263233285368993457879757738399643368354787234632745135155274525361620440
340532455653452155645722264385685523579545388464998647475936784423755683487133452753535123313604354
352152244221611331745526617386458872637865488449639856654753583624523335337272114515341203461045505
312033003454166053225452625154365544367483828667456379835556776344278883841652574422331646601235245
354330514213011046317425234714582372762675574834484437275552425744868747672133251365644033361512332
044021444522010141412515121117476227878457643873272338756238823637528547351444752605411146631314215
232413442230001100251223172352214643455635846823657522575345326788747411734266577565251164231145220
131504320306315625216346311325345143883224276853882277427888248757813625336722630560555630113015013
024451223151264563630623162617513241445834482436236846786748364751551471325535132540104323422105400
124420553125506323053405346614111134556285246233525828237347884315614217572766434350535521515101401
130200351112000250166105345316513263244676855323883356633222244252121772667652426014110503513555221
400241412230012102424516231766252561312255446621244373337355416351623124213640652213520151043110444
322440423504102454033133351036544434155137522762124773455775324625526553424524313460144053442544330
324303335402501236440552111053234114555641277255456252443511634243752643304353256666504353255522122
241434204225354100532033205000145461776516534477176114531271372735543451231303326454043352113013420
334124213300015522546105345511323376357312632521613774424256342133105114520414061451141133554301031
211304431150451251125020043053260563634374764226536222654742522421115640025331113215553523303422011
123324243323523142132445503461621106650437431517676417742653314226115466013230223030345414021112341
100121244242050431353422620202033056213213561562676642451361622463016135301034322504250003434240420
202331344430130314251310046312513630614136423111235233115234253411664210435541154434125202004300041
311142442400021052335032430131001522121243633355511015105456130306054020525345400331240114301233431
303333210333101445104545232532514231420245342044224564606555504302423403203123103211431303412444103
322112022033322224210512330414230625446520203533135512231066520423212314402311345154232414213413203
210220340333424334214524143431005502542403323230305103516410534415015522312221415410344021130000212

106
22/day9.py Executable file
View file

@ -0,0 +1,106 @@
#!/bin/python3
moves = []
with open('day9.txt', 'r') as f:
for line in f:
[direc, count] = line.split(' ')
count = int(count)
for i in range(count):
moves.append(direc)
hlocs = []
hloc = (0, 0)
for move in moves:
if move == 'U':
hloc = (hloc[0], hloc[1]-1)
if move == 'D':
hloc = (hloc[0], hloc[1]+1)
if move == 'R':
hloc = (hloc[0]+1, hloc[1])
if move == 'L':
hloc = (hloc[0]-1, hloc[1])
hlocs.append(hloc)
tlocs = set([(0, 0)])
tloc = (0, 0)
for hloc in hlocs:
if abs(hloc[0] - tloc[0]) > 1 or abs(hloc[1] - tloc[1]) > 1:
if hloc[0] < tloc[0]:
tloc = (tloc[0]-1, tloc[1])
elif hloc[0] > tloc[0]:
tloc = (tloc[0]+1, tloc[1])
if hloc[1] < tloc[1]:
tloc = (tloc[0], tloc[1]-1)
elif hloc[1] > tloc[1]:
tloc = (tloc[0], tloc[1]+1)
else:
if hloc[0] == tloc[0]+2:
tloc = (tloc[0]+1, tloc[1])
elif hloc[0] == tloc[0]-2:
tloc = (tloc[0]-1, tloc[1])
elif hloc[1] == tloc[1]+2:
tloc = (tloc[0], tloc[1]+1)
elif hloc[1] == tloc[1]-2:
tloc = (tloc[0], tloc[1]-1)
tlocs.add(tloc)
print("part1", len(tlocs))
def print_state(n, parts):
for y in range(-n, n):
for x in range(-n, n):
printed = False
for i in range(10):
if parts[i][0] == x and parts[i][1] == y:
printed = True
print(str(i), end='')
break
if not printed:
print('#', end='')
print('')
print('')
parts = []
visited = set([(0, 0)])
for i in range(10):
parts.append((0, 0))
for move in moves:
print(move)
for i in range(10):
if i == 0:
if move == 'R':
parts[0] = (parts[0][0]+1, parts[0][1])
if move == 'L':
parts[0] = (parts[0][0]-1, parts[0][1])
if move == 'U':
parts[0] = (parts[0][0], parts[0][1]-1)
if move == 'D':
parts[0] = (parts[0][0], parts[0][1]+1)
else:
(prevx, prevy) = parts[i-1]
if abs(prevx - parts[i][0]) > 1 or abs(prevy - parts[i][1]) > 1:
if prevx < parts[i][0]:
parts[i] = (parts[i][0]-1, parts[i][1])
elif prevx > parts[i][0]:
parts[i] = (parts[i][0]+1, parts[i][1])
if prevy < parts[i][1]:
parts[i] = (parts[i][0], parts[i][1]-1)
elif prevy > parts[i][1]:
parts[i] = (parts[i][0], parts[i][1]+1)
else:
if prevx == parts[i][0]+2:
parts[i] = (parts[i][0]+1, parts[i][1])
elif prevx == parts[i][0]-2:
parts[i] = (parts[i][0]-1, parts[i][1])
elif prevy == parts[i][1]+2:
parts[i] = (parts[i][0], parts[i][1]+1)
elif prevy == parts[i][1]-2:
parts[i] = (parts[i][0], parts[i][1]-1)
if i == 9:
visited.add(parts[-1])
print_state(5, parts)
print('part2', len(visited))

2000
22/day9.txt Normal file

File diff suppressed because it is too large Load diff

8
22/day9test.txt Normal file
View file

@ -0,0 +1,8 @@
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2

46
22/lib/day.ex Normal file
View file

@ -0,0 +1,46 @@
defmodule AOC.Day do
defmacro __using__(opts) do
day = Keyword.get(opts, :day)
debug = Keyword.get(opts, :debug, false)
trim = Keyword.get(opts, :trim, true)
input_file = Keyword.get(opts, :input, nil)
quote do
def solve do
input =
AOC.Day.input_lines(unquote(day), unquote(trim), unquote(input_file))
|> parse_input()
if unquote(debug) do
IO.inspect(input)
end
part1_solution = part1(input)
IO.puts("Part 1: #{part1_solution}")
part2_solution = part2(input)
IO.puts("Part 2: #{part2_solution}")
end
end
end
def input_lines(day, trim, input_file) do
lines =
input_file_name(day, input_file)
|> File.stream!()
if trim do
Enum.map(lines, &String.trim/1)
else
lines
end
end
def input_file_name(day, input_file) do
if input_file do
Path.join([File.cwd!(), "inputs", "day#{day}_#{input_file}.txt"])
else
Path.join([File.cwd!(), "inputs", "day#{day}.txt"])
end
end
end

24
22/lib/days/day1.ex Normal file
View file

@ -0,0 +1,24 @@
defmodule AOC.Day1 do
use AOC.Day, day: 1
def parse_input(lines) do
lines
|> Enum.chunk_by(&(&1 == ""))
|> Enum.reject(&(&1 == [""]))
|> Enum.map(fn l -> Enum.map(l, &String.to_integer/1) end)
end
def part1(input) do
input
|> Enum.map(&Enum.sum/1)
|> Enum.max()
end
def part2(input) do
input
|> Enum.map(&Enum.sum/1)
|> Enum.sort(:desc)
|> Enum.take(3)
|> Enum.sum()
end
end

93
22/lib/days/day11.ex Normal file
View file

@ -0,0 +1,93 @@
defmodule AOC.Day11 do
use AOC.Day, day: 11
alias AOC.Day11.{MonkeyRegistry, Monkey}
def parse_input(lines) do
monkeys =
lines
|> Enum.chunk_by(&(&1 == ""))
|> Enum.reject(&(&1 == [""]))
|> Enum.map(&parse_monkey/1)
supermod =
monkeys
|> Enum.map(& &1.modulo)
|> Enum.reduce(&Kernel.*/2)
{:ok, _} = Registry.start_link(keys: :unique, name: MonkeyRegistry)
Enum.each(monkeys, fn %{id: id} = monkey ->
GenServer.start_link(Monkey, Map.put(monkey, :supermod, supermod),
name: {:via, Registry, {MonkeyRegistry, id}}
)
end)
Enum.count(monkeys)
end
def parse_monkey([
"Monkey " <> monkey_id,
"Starting items: " <> items,
"Operation: " <> operation,
"Test: divisible by " <> modulo,
"If true: throw to monkey " <> true_monkey_id,
"If false: throw to monkey " <> false_monkey_id
]) do
%{
id: monkey_id |> String.slice(0..-2) |> String.to_integer(),
items: items |> String.split(", ") |> Enum.map(&String.to_integer/1),
operation: parse_operation(operation),
modulo: String.to_integer(modulo),
true_monkey_id: String.to_integer(true_monkey_id),
false_monkey_id: String.to_integer(false_monkey_id)
}
end
def parse_operation(operation) do
["new", "=", lhs, op, rhs] = String.split(operation)
fn old ->
lhs = if lhs == "old", do: old, else: String.to_integer(lhs)
rhs = if rhs == "old", do: old, else: String.to_integer(rhs)
op =
case op do
"*" -> &Kernel.*/2
"+" -> &Kernel.+/2
end
op.(lhs, rhs)
end
end
def part1(monkey_count) do
# monkey_business(monkey_count, 20, false)
end
def part2(monkey_count) do
monkey_business(monkey_count, 10000, true)
end
def monkey_business(monkey_count, rounds, ridiculous) do
execute_rounds(monkey_count, rounds, ridiculous)
Enum.map(0..(monkey_count - 1), fn id ->
[{pid, _}] = Registry.lookup(MonkeyRegistry, id)
Monkey.get_activeness(pid)
end)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.reduce(&Kernel.*/2)
end
def execute_rounds(monkey_count, rounds, ridiculous) do
Enum.each(0..(rounds - 1), fn _ -> execute_round(monkey_count, ridiculous) end)
end
def execute_round(monkey_count, ridiculous) do
Enum.each(0..(monkey_count - 1), fn id ->
[{pid, _}] = Registry.lookup(MonkeyRegistry, id)
Monkey.execute_turn(pid, ridiculous)
end)
end
end

View file

@ -0,0 +1,63 @@
defmodule AOC.Day11.Monkey do
use GenServer
alias AOC.Day11.{MonkeyRegistry, Monkey}
@impl true
def init(state) do
{:ok, Map.put(state, :activeness, 0)}
end
def execute_turn(pid, ridiculous) do
GenServer.call(pid, {:execute_turn, ridiculous}, :infinity)
end
def throw(pid, item) do
GenServer.call(pid, {:throw, item})
end
def get_items(pid) do
GenServer.call(pid, :get_items)
end
def get_activeness(pid) do
GenServer.call(pid, :get_activeness)
end
@impl true
def handle_call(
{:execute_turn, ridiculous},
_from,
%{
items: items,
operation: operation,
modulo: modulo,
true_monkey_id: true_monkey_id,
false_monkey_id: false_monkey_id,
activeness: activeness,
supermod: supermod
} = state
) do
Enum.each(items, fn item ->
item = operation.(item)
item = if ridiculous, do: item, else: Integer.floor_div(item, 3)
item = Integer.mod(item, supermod)
recipient = if Integer.mod(item, modulo) == 0, do: true_monkey_id, else: false_monkey_id
[{pid, _}] = Registry.lookup(MonkeyRegistry, recipient)
Monkey.throw(pid, item)
end)
{:reply, :ok, %{state | items: [], activeness: activeness + length(items)}}
end
def handle_call({:throw, item}, _from, %{items: items} = state) do
{:reply, :ok, Map.put(state, :items, items ++ [item])}
end
def handle_call(:get_items, _from, %{items: items} = state) do
{:reply, items, state}
end
def handle_call(:get_activeness, _from, %{activeness: activeness} = state) do
{:reply, activeness, state}
end
end

67
22/lib/days/day13.ex Normal file
View file

@ -0,0 +1,67 @@
defmodule AOC.Day13 do
use AOC.Day, day: 13
alias AOC.Day13.Packet
defmodule Packet do
def compare({p1, p2}) do
compare(p1, p2)
end
def compare([], []), do: :eq
def compare([], _l), do: :lt
def compare(_l, []), do: :gt
def compare(x, l) when is_number(x) and is_list(l), do: compare([x], l)
def compare(l, x) when is_number(x) and is_list(l), do: compare(l, [x])
def compare([h1 | tl1], [h2 | tl2]) do
case compare(h1, h2) do
:eq -> compare(tl1, tl2)
x -> x
end
end
def compare(x, y) when x < y, do: :lt
def compare(x, y) when x == y, do: :eq
def compare(x, y) when x > y, do: :gt
end
def parse_input(lines) do
lines
|> Enum.chunk_by(&(&1 == ""))
|> Enum.reject(&(&1 == [""]))
|> Enum.map(fn [p1, p2] ->
{parse_packet(p1), parse_packet(p2)}
end)
end
def parse_packet(packet) do
packet
|> String.to_charlist()
|> Code.string_to_quoted()
|> elem(1)
end
def part1(pairs) do
pairs
|> Enum.map(&Packet.compare/1)
|> Enum.with_index()
|> Enum.filter(&(elem(&1, 0) == :lt))
|> Enum.map(&elem(&1, 1))
|> Enum.map(&Kernel.+(&1, 1))
|> Enum.sum()
end
@divider_packets [[[2]], [[6]]]
def part2(pairs) do
pairs
|> Enum.flat_map(fn {p1, p2} -> [p1, p2] end)
|> Kernel.++(@divider_packets)
|> Enum.sort(Packet)
|> Enum.with_index()
|> Enum.filter(&(elem(&1, 0) in @divider_packets))
|> Enum.map(&elem(&1, 1))
|> Enum.map(&Kernel.+(&1, 1))
|> Enum.reduce(&Kernel.*/2)
end
end

81
22/lib/days/day16.ex Normal file
View file

@ -0,0 +1,81 @@
defmodule AOC.Day16 do
use AOC.Day, day: 16, debug: true, input: "example"
alias AOC.Day16.{ValveRegistry, Valve}
defmodule Valve do
use GenServer
@impl true
def init(state) do
state = Map.put(state, :released, 0)
state = Map.put(state, :open?, false)
{:ok, state}
end
def get_rate(valve) do
GenServer.call(valve, :get_rate)
end
def tick(valve) do
GenServer.call(valve, :tick)
end
def open(valve) do
GenServer.call(valve, :open)
end
@impl true
def handle_call(:get_rate, _from, %{rate: rate} = state) do
{:reply, rate, state}
end
def handle_call(:tick, _from, %{open?: open?, released: released, rate: rate} = state) do
released = if open?, do: released + rate, else: released
{:reply, :ok, %{state | released: released}}
end
def handle_call(:open, _from, state) do
{:reply, :ok, %{state | open?: true}}
end
end
@line_regex ~r/Valve (?<name>[A-Z]{2}) has flow rate=(?<rate>\d+); tunnels? leads? to valves? (?<tunnels>.*)/
def parse_input(lines) do
{:ok, _} = Registry.start_link(keys: :unique, name: ValveRegistry)
lines
|> Enum.map(fn line ->
%{"name" => name, "rate" => rate, "tunnels" => tunnels} =
Regex.named_captures(@line_regex, line)
%{name: name, rate: String.to_integer(rate), tunnels: String.split(tunnels, ", ")}
end)
|> Enum.into(%{}, fn %{name: name} = valve ->
{:ok, pid} =
GenServer.start_link(Valve, valve, name: {:via, Registry, {ValveRegistry, name}})
{name, pid}
end)
end
def part1(valves) do
Enum.map(valves, fn {name, valve} ->
rate = Valve.get_rate(valve)
{name, rate}
end)
|> Enum.sort_by(&elem(&1, 1), :desc)
|> IO.inspect()
:ok
end
def tick(valves) do
Enum.each(valves, fn {_, valve} ->
Valve.tick(valve)
end)
end
def part2(_input) do
end
end

68
22/lib/days/day2.ex Normal file
View file

@ -0,0 +1,68 @@
defmodule AOC.Day2 do
use AOC.Day, day: 2
def parse_input(lines) do
Enum.map(lines, fn line ->
[move1, move2] =
line
|> String.split(" ")
|> Enum.map(fn c ->
c
|> String.to_charlist()
|> hd()
end)
{move1, move2}
end)
end
def part1(input) do
input
|> Enum.map(fn {move1, move2} ->
score =
case move2 do
?X -> 1
?Y -> 2
?Z -> 3
end
score +
case {move1, move2} do
{?A, ?Y} -> 6
{?B, ?Z} -> 6
{?C, ?X} -> 6
{?A, ?X} -> 3
{?B, ?Y} -> 3
{?C, ?Z} -> 3
_ -> 0
end
end)
|> Enum.sum()
end
def part2(input) do
input
|> Enum.map(fn {move1, result} ->
score =
case result do
?X -> 0
?Y -> 3
?Z -> 6
end
score +
case {move1, result} do
{?A, ?X} -> 3
{?A, ?Y} -> 1
{?A, ?Z} -> 2
{?B, ?X} -> 1
{?B, ?Y} -> 2
{?B, ?Z} -> 3
{?C, ?X} -> 2
{?C, ?Y} -> 3
{?C, ?Z} -> 1
end
end)
|> Enum.sum()
end
end

39
22/lib/days/day3.ex Normal file
View file

@ -0,0 +1,39 @@
defmodule AOC.Day3 do
use AOC.Day, day: 3
def parse_input(lines), do: lines
def part1(input) do
input
|> Enum.map(fn line ->
comp_size = line |> String.length() |> div(2)
{comp1, comp2} = line |> String.to_charlist() |> Enum.split(comp_size)
comp1 = MapSet.new(comp1)
comp2 = MapSet.new(comp2)
MapSet.intersection(comp1, comp2)
|> MapSet.to_list()
|> hd()
|> get_priority()
end)
|> Enum.sum()
end
def part2(input) do
input
|> Enum.chunk_every(3)
|> Enum.map(fn group ->
group
|> Enum.map(&String.to_charlist/1)
|> Enum.map(&MapSet.new/1)
|> Enum.reduce(&MapSet.intersection/2)
|> MapSet.to_list()
|> hd()
|> get_priority()
end)
|> Enum.sum()
end
def get_priority(char) when char >= ?a, do: char - 96
def get_priority(char), do: char - 38
end

38
22/lib/days/day4.ex Normal file
View file

@ -0,0 +1,38 @@
defmodule AOC.Day4 do
use AOC.Day, day: 4
def parse_input(lines) do
Enum.map(lines, fn line ->
[assign1, assign2] =
line
|> String.split(",")
|> Enum.map(fn assign ->
[from, to] =
assign
|> String.split("-")
|> Enum.map(&String.to_integer/1)
MapSet.new(from..to)
end)
{assign1, assign2}
end)
end
def part1(input) do
input
|> Enum.map(fn {set1, set2} ->
MapSet.subset?(set1, set2) or MapSet.subset?(set2, set1)
end)
|> Enum.count(& &1)
end
def part2(input) do
input
|> Enum.map(fn {set1, set2} ->
MapSet.intersection(set1, set2)
|> Enum.any?()
end)
|> Enum.count(& &1)
end
end

70
22/lib/days/day5.ex Normal file
View file

@ -0,0 +1,70 @@
defmodule AOC.Day5 do
use AOC.Day, day: 5, trim: false
def parse_input(lines) do
[init, ops] =
lines
|> Enum.chunk_by(&(&1 == "\n"))
|> Enum.reject(&(&1 == ["\n"]))
{parse_init(init), parse_ops(ops)}
end
def parse_init(init) do
init
|> Enum.split(-1)
|> elem(0)
|> Enum.map(fn line ->
line
|> String.to_charlist()
|> tl()
|> Enum.take_every(4)
end)
|> Enum.zip_with(&Function.identity/1)
|> Enum.map(fn stack ->
Enum.reject(stack, &(&1 == ?\s))
end)
end
def parse_ops(ops) do
Enum.map(ops, fn op ->
%{"count" => count, "from" => from, "to" => to} =
Regex.named_captures(~r/move (?<count>\d+) from (?<from>\d+) to (?<to>\d+)\n/, op)
%{
count: String.to_integer(count),
from: String.to_integer(from) - 1,
to: String.to_integer(to) - 1
}
end)
end
def part1(input) do
move(input, true)
end
def part2(input) do
move(input, false)
end
def move({init, ops}, reverse) do
ops
|> Enum.reduce(init, fn %{count: count, from: from, to: to}, acc ->
{acc, removed} = remove(acc, from, count)
removed = if reverse, do: Enum.reverse(removed), else: removed
add(acc, to, removed)
end)
|> Enum.map(&hd/1)
end
def remove(stacks, from, count) do
{removed, new} = Enum.split(Enum.at(stacks, from), count)
stacks = List.update_at(stacks, from, fn _ -> new end)
{stacks, removed}
end
def add(stacks, to, removed) do
added = Enum.concat(removed, Enum.at(stacks, to))
List.update_at(stacks, to, fn _ -> added end)
end
end

29
22/lib/days/day6.ex Normal file
View file

@ -0,0 +1,29 @@
defmodule AOC.Day6 do
use AOC.Day, day: 6
def parse_input(lines), do: lines |> hd() |> String.to_charlist()
def part1(input) do
find_marker(input, 0, 4)
end
def part2(input) do
find_marker(input, 0, 14)
end
def find_marker(signal, index, length) do
distinct =
signal
|> Enum.take(length)
|> MapSet.new()
|> Enum.count()
if distinct == length do
index + length
else
signal
|> tl()
|> find_marker(index + 1, length)
end
end
end

82
22/lib/days/day7.ex Normal file
View file

@ -0,0 +1,82 @@
defmodule AOC.Day7 do
use AOC.Day, day: 7
def parse_input(lines) do
Enum.map(lines, fn
"$ cd " <> arg ->
{:cd, arg}
"$ ls" ->
:ls
"dir " <> arg ->
{:dir, arg}
line ->
[size, file] = String.split(line)
{:file, String.to_integer(size), file}
end)
end
def part1(input) do
input
|> get_file_sizes()
|> get_directory_sizes()
|> Enum.map(fn {_, size} -> size end)
|> Enum.filter(&(&1 <= 100_000))
|> Enum.sum()
end
def part2(input) do
file_sizes = get_file_sizes(input)
total_size =
file_sizes
|> Enum.map(&elem(&1, 1))
|> Enum.sum()
size_needed = total_size - 40_000_000
file_sizes
|> get_directory_sizes()
|> Enum.map(&elem(&1, 1))
|> Enum.filter(fn size -> size >= size_needed end)
|> Enum.min()
end
def get_file_sizes(input) do
Enum.reduce(input, {[], []}, fn
{:cd, "/"}, {_, sizes} ->
{["/"], sizes}
{:cd, ".."}, {[_ | path], sizes} ->
{path, sizes}
{:cd, dir}, {path, sizes} ->
{[dir | path], sizes}
{:file, size, file}, {path, sizes} ->
{path, [{[file | path], size} | sizes]}
_, acc ->
acc
end)
|> elem(1)
end
def get_directory_sizes(file_sizes) do
file_sizes
|> Enum.reduce(%{}, fn {[_ | path], size}, acc ->
path = Enum.reverse(path)
update_directory_size(acc, size, path)
end)
end
def update_directory_size(sizes, file_size, file_path) do
Enum.reduce(file_path, {sizes, []}, fn dir, {sizes, cur_path} ->
cur_path = [dir | cur_path]
{Map.update(sizes, cur_path, file_size, &(&1 + file_size)), cur_path}
end)
|> elem(0)
end
end

53
22/lib/days/day8.ex Normal file
View file

@ -0,0 +1,53 @@
defmodule AOC.Day8 do
use AOC.Day, day: 8, debug: true
def parse_input(lines) do
Enum.map(lines, fn line ->
line
|> String.split("", trim: true)
|> Enum.map(&String.to_integer/1)
end)
end
def part1(rows) do
rows = with_index(rows)
cols = Enum.zip(rows) |> Enum.map(&Tuple.to_list/1)
rrows = Enum.map(rows, &Enum.reverse/1)
rcols = Enum.map(cols, &Enum.reverse/1)
[rows, cols, rrows, rcols]
|> Enum.map(&count_visible/1)
|> Enum.reduce(&MapSet.union/2)
|> Enum.count()
end
def part2(rows) do
rows
end
def with_index(rows) do
Enum.with_index(rows, fn row, y ->
Enum.with_index(row, fn el, x ->
%{height: el, x: x, y: y}
end)
end)
end
def count_visible(rows) do
rows
|> Enum.map(&count_visible_row/1)
|> Enum.map(&elem(&1, 0))
|> Enum.reduce(&MapSet.union/2)
end
def count_visible_row(row) do
Enum.reduce(row, {MapSet.new(), -1}, fn
%{height: h, x: x, y: y}, {trees, highest} when h > highest ->
{MapSet.put(trees, {x, y}), h}
_, acc ->
acc
end)
end
end

28
22/mix.exs Normal file
View file

@ -0,0 +1,28 @@
defmodule Aoc2022.MixProject do
use Mix.Project
def project do
[
app: :aoc2022,
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end