Update python solutions

This commit is contained in:
Felix Bargfeldt 2023-10-20 12:55:09 +02:00
parent 083e284d1a
commit 26a81da2f3
Signed by: Defelo
GPG key ID: 2A05272471204DD3
25 changed files with 438 additions and 661 deletions

View file

@ -1,15 +1,8 @@
from utils.parsing import ints
from lib import *
input = read_input(2022, 1)
def part1(puzzle: str):
return max(sum(ints(p)) for p in puzzle.split("\n\n"))
nums = [sum(ints(p)) for p in input.split("\n\n")]
def part2(puzzle: str):
return sum(sorted([sum(ints(p)) for p in puzzle.split("\n\n")], reverse=True)[:3])
if __name__ == "__main__":
from aoc import run
run(2022, 1, part1, part2)
print(max(nums))
print(sum(sorted(nums, reverse=True)[:3]))

View file

@ -1,36 +1,29 @@
def part1(puzzle: str):
out = 0
for line in puzzle.splitlines():
a, b = line.split()
a = ord(a) - ord("A")
b = ord(b) - ord("X")
w = (a - b) % 3
if w == 0:
out += 3 + b + 1
elif w == 1:
out += b + 1
elif w == 2:
out += 6 + b + 1
return out
from lib import *
input = read_input(2022, 2)
def part2(puzzle: str):
out = 0
for line in puzzle.splitlines():
a, b = line.split()
a = ord(a) - ord("A")
b = ord(b) - ord("X")
w = (a + b - 1) % 3
if b == 0:
out += w + 1
elif b == 1:
out += 3 + w + 1
elif b == 2:
out += 6 + w + 1
return out
part1 = 0
part2 = 0
for line in input.splitlines():
a, b = line.split()
a = ord(a) - ord("A")
b = ord(b) - ord("X")
w = (a - b) % 3
if w == 0:
part1 += 3 + b + 1
elif w == 1:
part1 += b + 1
elif w == 2:
part1 += 6 + b + 1
if __name__ == "__main__":
from aoc import run
w = (a + b - 1) % 3
if b == 0:
part2 += w + 1
elif b == 1:
part2 += 3 + w + 1
elif b == 2:
part2 += 6 + w + 1
run(2022, 2, part1, part2)
print(part1)
print(part2)

View file

@ -1,19 +1,19 @@
def part1(puzzle: str):
return sum(
from lib import *
input = read_input(2022, 3)
lines = input.splitlines()
print(
sum(
ord((x := ({*line[: len(line) // 2]} & {*line[len(line) // 2 :]}).pop()).upper()) - 64 + 26 * x.isupper()
for line in puzzle.splitlines()
for line in lines
)
)
def part2(puzzle: str):
lines = puzzle.splitlines()
return sum(
print(
sum(
ord((x := ({*lines[i]} & {*lines[i + 1]} & {*lines[i + 2]}).pop()).upper()) - 64 + 26 * x.isupper()
for i in range(0, len(lines), 3)
)
if __name__ == "__main__":
from aoc import run
run(2022, 3, part1, part2)
)

View file

@ -1,15 +1,9 @@
from utils.parsing import pints
from lib import *
input = read_input(2022, 4)
lines = [*map(pints, input.splitlines())]
def part1(puzzle: str):
return sum(a <= c <= d <= b or c <= a <= b <= d for a, b, c, d in map(pints, puzzle.splitlines()))
def part2(puzzle: str):
return sum(d >= a and c <= b for a, b, c, d in map(pints, puzzle.splitlines()))
if __name__ == "__main__":
from aoc import run
run(2022, 4, part1, part2)
print(sum(a <= c <= d <= b or c <= a <= b <= d for a, b, c, d in lines))
print(sum(d >= a and c <= b for a, b, c, d in lines))

View file

@ -1,36 +1,28 @@
from utils.parsing import pints
from lib import *
input = read_input(2022, 5)
initial, instructions = input.split("\n\n")
nums, *initial = initial.splitlines()[::-1]
stacks = [[] for _ in pints(nums)]
for line in initial:
for i in range(0, len(line), 4):
if line[i + 1] != " ":
stacks[i // 4].append(line[i + 1])
instructions = [(cnt, i - 1, j - 1) for cnt, i, j in map(pints, instructions.splitlines())]
def get_input(puzzle: str) -> tuple[list[list[str]], list[tuple[int, int, int]]]:
initial, instructions = puzzle.split("\n\n")
nums, *initial = initial.splitlines()[::-1]
stacks: list[list[str]] = [[] for _ in pints(nums)]
for line in initial:
for i in range(0, len(line), 4):
if line[i + 1] != " ":
stacks[i // 4].append(line[i + 1])
return stacks, [(cnt, i - 1, j - 1) for cnt, i, j in map(pints, instructions.splitlines())]
_stacks = deepcopy(stacks)
for cnt, i, j in instructions:
for _ in range(cnt):
stacks[j].append(stacks[i].pop())
print("".join(s[-1] for s in stacks))
def part1(puzzle: str):
stacks, instructions = get_input(puzzle)
for cnt, i, j in instructions:
for _ in range(cnt):
stacks[j].append(stacks[i].pop())
return "".join(s[-1] for s in stacks)
def part2(puzzle: str):
stacks, instructions = get_input(puzzle)
for cnt, i, j in instructions:
stacks[j] += stacks[i][-cnt:]
del stacks[i][-cnt:]
return "".join(s[-1] for s in stacks)
if __name__ == "__main__":
from aoc import run
run(2022, 5, part1, part2)
stacks = _stacks
for cnt, i, j in instructions:
stacks[j] += stacks[i][-cnt:]
del stacks[i][-cnt:]
print("".join(s[-1] for s in stacks))

View file

@ -1,20 +1,15 @@
def get_input(puzzle: str) -> str:
return puzzle
from lib import *
input = read_input(2022, 6)
def part1(puzzle: str):
for i in range(4, len(puzzle)):
if len(set(puzzle[i - 4 : i])) == 4:
return i
for i in range(4, len(input)):
if len(set(input[i - 4 : i])) == 4:
print(i)
break
def part2(puzzle: str):
for i in range(14, len(puzzle)):
if len(set(puzzle[i - 14 : i])) == 14:
return i
if __name__ == "__main__":
from aoc import run
run(2022, 6, part1, part2)
for i in range(14, len(input)):
if len(set(input[i - 14 : i])) == 14:
print(i)
break

View file

@ -1,53 +1,36 @@
import re
from lib import *
input = read_input(2022, 7)
sizes = {}
seen = set()
pwd = ()
for line in input.splitlines():
if match := re.match(r"^\$ cd (.+)$", line):
d = match[1]
if d == "/":
pwd = ()
elif d == "..":
pwd = pwd[:-1]
else:
pwd = (*pwd, d)
elif match := re.match(r"^(\d+) (.+)$", line):
s = int(match[1])
n = match[2]
if (pwd, n) in seen:
continue
seen.add((pwd, n))
d = pwd
while True:
sizes[d] = sizes.get(d, 0) + s
if not d:
break
d = d[:-1]
def get_input(puzzle: str) -> list[str]:
return puzzle.splitlines()
print(sum(s for s in sizes.values() if s <= 100000))
def get_dir_sizes(lines: list[str]) -> dict[tuple[str, ...], int]:
out = {}
seen = set()
pwd = ()
for line in lines:
if match := re.match(r"^\$ cd (.+)$", line):
d = match[1]
if d == "/":
pwd = ()
elif d == "..":
pwd = pwd[:-1]
else:
pwd = (*pwd, d)
elif match := re.match(r"^(\d+) (.+)$", line):
s = int(match[1])
n = match[2]
if (pwd, n) in seen:
continue
seen.add((pwd, n))
d = pwd
while True:
out[d] = out.get(d, 0) + s
if not d:
break
d = d[:-1]
return out
def part1(puzzle: str):
sizes = get_dir_sizes(get_input(puzzle))
return sum(s for s in sizes.values() if s <= 100000)
def part2(puzzle: str):
sizes = get_dir_sizes(get_input(puzzle))
free = 70000000 - sizes[()]
return min(s for s in sizes.values() if s >= 30000000 - free)
if __name__ == "__main__":
from aoc import run
run(2022, 7, part1, part2)
free = 70000000 - sizes[()]
print(min(s for s in sizes.values() if s >= 30000000 - free))

View file

@ -1,51 +1,40 @@
from utils.grid import NEIGH_DIRECT
from lib import *
input = read_input(2022, 8)
grid = [[*map(int, line)] for line in input.splitlines()]
def get_input(puzzle: str) -> list[list[int]]:
return [[*map(int, line)] for line in puzzle.splitlines()]
w, h = len(grid[0]), len(grid)
visible = set()
for dx, dy in NEIGH_DIRECT:
for y in range(h):
for x in range(w):
i = y + dy
j = x + dx
while i in range(h) and j in range(w):
if grid[i][j] >= grid[y][x]:
break
i += dy
j += dx
else:
visible.add((x, y))
print(len(visible))
def part1(puzzle: str):
grid = get_input(puzzle)
w, h = len(grid[0]), len(grid)
visible = set()
for dx, dy in NEIGH_DIRECT:
for y in range(h):
for x in range(w):
i = y + dy
j = x + dx
while i in range(h) and j in range(w):
if grid[i][j] >= grid[y][x]:
break
i += dy
j += dx
else:
visible.add((x, y))
return len(visible)
def part2(puzzle: str):
grid = get_input(puzzle)
out = 0
for y in range(len(grid)):
for x in range(len(grid[y])):
d = 1
for dx, dy in NEIGH_DIRECT:
i = y + dy
j = x + dx
s = 0
while i in range(len(grid)) and j in range(len(grid[0])):
s += 1
if grid[i][j] >= grid[y][x]:
break
i += dy
j += dx
d *= s
out = max(out, d)
return out
if __name__ == "__main__":
from aoc import run
run(2022, 8, part1, part2)
out = 0
for y in range(len(grid)):
for x in range(len(grid[y])):
d = 1
for dx, dy in NEIGH_DIRECT:
i = y + dy
j = x + dx
s = 0
while i in range(len(grid)) and j in range(len(grid[0])):
s += 1
if grid[i][j] >= grid[y][x]:
break
i += dy
j += dx
d *= s
out = max(out, d)
print(out)

View file

@ -1,8 +1,8 @@
from utils.grid import NEIGH_DICT
from lib import *
input = read_input(2022, 9)
def get_input(puzzle: str) -> list[tuple[int, int, int]]:
return [(*NEIGH_DICT[line[0]], int(line[2:])) for line in puzzle.splitlines()]
lines = [(*NEIGH_DICT[line[0]], int(line[2:])) for line in input.splitlines()]
def solve(lines, n):
@ -21,17 +21,5 @@ def solve(lines, n):
return len(visited)
def part1(puzzle: str):
lines = get_input(puzzle)
return solve(lines, 2)
def part2(puzzle: str):
lines = get_input(puzzle)
return solve(lines, 10)
if __name__ == "__main__":
from aoc import run
run(2022, 9, part1, part2)
print(solve(lines, 2))
print(solve(lines, 10))

View file

@ -1,11 +1,11 @@
from utils.parsing import ints, parse_ascii
from lib import *
input = read_input(2022, 10)
instructions = [next(iter(ints(line)), None) for line in input.splitlines()]
def get_input(puzzle: str) -> list[int | None]:
return [next(iter(ints(line)), None) for line in puzzle.splitlines()]
def execute(instructions: list[int | None]):
def execute():
x = 1
i = 0
for inst in instructions:
@ -17,15 +17,5 @@ def execute(instructions: list[int | None]):
x += inst
def part1(puzzle: str):
return sum((i + 1) * x for i, x in execute(get_input(puzzle)) if i % 40 == 19)
def part2(puzzle: str):
return parse_ascii({(i // 40, i % 40) for i, x in execute(get_input(puzzle)) if abs(i % 40 - x) <= 1})
if __name__ == "__main__":
from aoc import run
run(2022, 10, part1, part2)
print(sum((i + 1) * x for i, x in execute() if i % 40 == 19))
print(parse_ascii({(i // 40, i % 40) for i, x in execute() if abs(i % 40 - x) <= 1}))

View file

@ -1,20 +1,19 @@
from utils.parsing import ints
import math
from lib import *
input = read_input(2022, 11)
def get_input(puzzle: str) -> tuple[list[tuple[str, int, int, int]], list[list[int]]]:
monkeys = []
items = []
for monkey in puzzle.split("\n\n"):
lines = monkey.splitlines()
starting = ints(lines[1])
(op,) = lines[2].split(maxsplit=3)[3:]
(test,) = ints(lines[3])
(tt,) = ints(lines[4])
(tf,) = ints(lines[5])
monkeys.append((op, test, tt, tf))
items.append(starting)
return monkeys, items
monkeys = []
items = []
for monkey in input.split("\n\n"):
lines = monkey.splitlines()
starting = ints(lines[1])
(op,) = lines[2].split(maxsplit=3)[3:]
(test,) = ints(lines[3])
(tt,) = ints(lines[4])
(tf,) = ints(lines[5])
monkeys.append((op, test, tt, tf))
items.append(starting)
def simulate(monkeys, items, rounds, div3):
@ -34,17 +33,5 @@ def simulate(monkeys, items, rounds, div3):
return cnt[-1] * cnt[-2]
def part1(puzzle: str):
monkeys, items = get_input(puzzle)
return simulate(monkeys, items, 20, True)
def part2(puzzle: str):
monkeys, items = get_input(puzzle)
return simulate(monkeys, items, 10000, False)
if __name__ == "__main__":
from aoc import run
run(2022, 11, part1, part2)
print(simulate(monkeys, deepcopy(items), 20, True))
print(simulate(monkeys, items, 10000, False))

View file

@ -1,21 +1,21 @@
from utils.grid import get_neighbors
from lib import *
input = read_input(2022, 12)
def get_input(puzzle: str) -> tuple[tuple[int, int], tuple[int, int], list[list[int]]]:
grid = []
start = None
end = None
for i, line in enumerate(puzzle.splitlines()):
grid.append([ord(c) - 97 if c not in "SE" else {"S": 0, "E": 25}[c] for c in line])
if "S" in line:
start = line.index("S"), i
if "E" in line:
end = line.index("E"), i
assert start and end
return start, end, grid
grid = []
start = None
end = None
for i, line in enumerate(input.splitlines()):
grid.append([ord(c) - 97 if c not in "SE" else {"S": 0, "E": 25}[c] for c in line])
if "S" in line:
start = line.index("S"), i
if "E" in line:
end = line.index("E"), i
assert start and end
def bfs(start, grid, target, step):
def bfs(start: tuple[int, int], grid, target, step):
queue: list[tuple[int, int, int]] = [(0, *start)]
visited = set()
while queue:
@ -33,17 +33,5 @@ def bfs(start, grid, target, step):
queue.append((d + 1, p, q))
def part1(puzzle: str):
start, end, grid = get_input(puzzle)
return bfs(start, grid, lambda x, y: (x, y) == end, lambda x, y, p, q: grid[q][p] - grid[y][x] <= 1)
def part2(puzzle: str):
_, end, grid = get_input(puzzle)
return bfs(end, grid, lambda x, y: grid[y][x] == 0, lambda x, y, p, q: grid[y][x] - grid[q][p] <= 1)
if __name__ == "__main__":
from aoc import run
run(2022, 12, part1, part2)
print(bfs(start, grid, lambda x, y: (x, y) == end, lambda x, y, p, q: grid[q][p] - grid[y][x] <= 1))
print(bfs(end, grid, lambda x, y: grid[y][x] == 0, lambda x, y, p, q: grid[y][x] - grid[q][p] <= 1))

View file

@ -1,8 +1,8 @@
def get_input(puzzle: str) -> list[tuple[list, list]]:
out = []
for pair in puzzle.split("\n\n"):
out.append(tuple(map(eval, pair.splitlines())))
return out
from lib import *
input = read_input(2022, 13)
pairs = [tuple(map(eval, pair.splitlines())) for pair in input.split("\n\n")]
def compare(a, b):
@ -28,19 +28,10 @@ def compare(a, b):
return None
def part1(puzzle: str):
return sum((i + 1) * (compare(a, b) is True) for i, (a, b) in enumerate(get_input(puzzle)))
print(sum((i + 1) * (compare(a, b) is True) for i, (a, b) in enumerate(pairs)))
def part2(puzzle: str):
packets = [[[2]], [[6]]] + [x for a, b in get_input(puzzle) for x in [a, b]]
out = sum(compare(packet, packets[0]) is True for packet in packets) + 1
out *= sum(compare(packet, packets[1]) is True for packet in packets) + 1
return out
if __name__ == "__main__":
from aoc import run
run(2022, 13, part1, part2)
packets = [[[2]], [[6]]] + [x for a, b in pairs for x in [a, b]]
out = sum(compare(packet, packets[0]) is True for packet in packets) + 1
out *= sum(compare(packet, packets[1]) is True for packet in packets) + 1
print(out)

View file

@ -1,16 +1,15 @@
from utils.grid import iter_line
from utils.list import sliding_window
from lib import *
input = read_input(2022, 14)
def get_input(puzzle: str) -> tuple[set[tuple[int, int]], int]:
rock = set()
maxy = 0
for line in puzzle.splitlines():
for a, b in sliding_window(line.split(" -> ")):
for x, y in iter_line(*map(int, a.split(",")), *map(int, b.split(","))):
rock.add((x, y))
maxy = max(y, maxy)
return rock, maxy
rock = set()
maxy = 0
for line in input.splitlines():
for a, b in sliding_window(line.split(" -> ")):
for x, y in iter_line(*map(int, a.split(",")), *map(int, b.split(","))):
rock.add((x, y))
maxy = max(y, maxy)
def simulate(x, y, air, maxy):
@ -28,23 +27,13 @@ def simulate(x, y, air, maxy):
return None
def part1(puzzle: str):
rock, maxy = get_input(puzzle)
sand = set()
while s := simulate(500, 0, lambda x, y: (x, y) not in rock and (x, y) not in sand, maxy):
sand.add(s)
return len(sand)
sand = set()
while s := simulate(500, 0, lambda x, y: (x, y) not in rock and (x, y) not in sand, maxy):
sand.add(s)
print(len(sand))
def part2(puzzle: str):
rock, maxy = get_input(puzzle)
sand = set()
while (500, 0) not in sand:
sand.add(simulate(500, 0, lambda x, y: y < maxy + 2 and (x, y) not in rock and (x, y) not in sand, maxy + 2))
return len(sand)
if __name__ == "__main__":
from aoc import run
run(2022, 14, part1, part2)
sand = set()
while (500, 0) not in sand:
sand.add(simulate(500, 0, lambda x, y: y < maxy + 2 and (x, y) not in rock and (x, y) not in sand, maxy + 2))
print(len(sand))

View file

@ -1,56 +1,44 @@
from itertools import chain
from utils.grid import iter_line
from utils.parsing import ints
from lib import *
input = read_input(2022, 15)
lines = [tuple(ints(line)) for line in input.splitlines()]
def get_input(puzzle: str) -> list[tuple[int, int, int, int]]:
return [tuple(ints(line)) for line in puzzle.splitlines()]
ty = 2000000
out = set()
beacons = set()
for sx, sy, bx, by in lines:
d = abs(sx - bx) + abs(sy - by)
a = sx - (d - abs(sy - ty))
b = sx + (d - abs(sy - ty))
out.update(range(a, b + 1))
if by == ty:
beacons.add(bx)
print(len(out - beacons))
def part1(puzzle: str):
inp = get_input(puzzle)
ty = 2000000
out = set()
beacons = set()
for sx, sy, bx, by in inp:
d = abs(sx - bx) + abs(sy - by)
a = sx - (d - abs(sy - ty))
b = sx + (d - abs(sy - ty))
out.update(range(a, b + 1))
if by == ty:
beacons.add(bx)
sensors = []
minx = 1e1337
miny = 1e1337
maxx = -1e1337
maxy = -1e1337
for sx, sy, bx, by in lines:
d = abs(sx - bx) + abs(sy - by)
sensors.append((sx, sy, d))
return len(out - beacons)
minx = min(sx, minx)
miny = min(sy, miny)
maxx = max(sx, maxx)
maxy = max(sy, maxy)
def part2(puzzle: str):
inp = get_input(puzzle)
sensors = []
minx = 1e1337
miny = 1e1337
maxx = -1e1337
maxy = -1e1337
for sx, sy, bx, by in inp:
d = abs(sx - bx) + abs(sy - by)
sensors.append((sx, sy, d))
minx = min(sx, minx)
miny = min(sy, miny)
maxx = max(sx, maxx)
maxy = max(sy, maxy)
for sx, sy, d in sensors:
for x, y in chain(
iter_line(sx, sy - d - 1, sx + d + 1, sy),
iter_line(sx + d + 1, sy, sx, sy + d + 1),
iter_line(sx, sy + d + 1, sx - d - 1, sy),
iter_line(sx - d - 1, sy, sx, sy - d - 1),
):
if minx <= x <= maxx and miny <= y <= maxy and all(abs(x - a) + abs(y - b) > d for a, b, d in sensors):
return x * 4000000 + y
if __name__ == "__main__":
from aoc import run
run(2022, 15, part1, part2)
for sx, sy, d in sensors:
for x, y in itertools.chain(
iter_line(sx, sy - d - 1, sx + d + 1, sy),
iter_line(sx + d + 1, sy, sx, sy + d + 1),
iter_line(sx, sy + d + 1, sx - d - 1, sy),
iter_line(sx - d - 1, sy, sx, sy - d - 1),
):
if minx <= x <= maxx and miny <= y <= maxy and all(abs(x - a) + abs(y - b) > d for a, b, d in sensors):
print(x * 4000000 + y)
break

View file

@ -1,83 +1,56 @@
from functools import cache, reduce
import re
from lib import *
input = read_input(2022, 16)
n = len(input.splitlines())
graph = [set() for _ in range(n)]
rates = [0] * n
names = {"AA": 0}
def get_input(puzzle: str) -> tuple[list[int], list[set[int]]]:
n = len(puzzle.splitlines())
graph = [set() for _ in range(n)]
rates = [0] * n
names = {"AA": 0}
def name(n: str) -> int:
return names.setdefault(n, len(names))
for line in puzzle.splitlines():
v, r, n = re.match(r"^Valve (.+) has flow rate=(\d+); tunnels? leads? to valves? (.+)$", line).groups() # type: ignore
v = name(v)
r = int(r)
n = list(map(name, n.split(", ")))
rates[v] = r
graph[v].update(n)
return rates, graph
def name(n: str) -> int:
return names.setdefault(n, len(names))
def part1(puzzle: str):
rates, graph = get_input(puzzle)
n = len(rates)
dist = {i: {j: 0 if i == j else 1 if j in graph[i] else 1e1337 for j in range(n)} for i in range(n)}
for k in range(n):
for i in range(n):
for j in range(n):
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
@cache
def solve(p, time, closed):
if time <= 0:
return 0
out = 0
for q in range(n):
if closed & 1 << q == 0:
continue
t = time - dist[p][q] - 1
out = max(out, solve(q, t, closed & ~(1 << q)) + rates[q] * t)
return out
return solve(0, 30, reduce(lambda acc, x: acc | 1 << x, (i for i in range(n) if rates[i]), 0))
for line in input.splitlines():
v, r, n = re.match(r"^Valve (.+) has flow rate=(\d+); tunnels? leads? to valves? (.+)$", line).groups() # type: ignore
v = name(v)
r = int(r)
n = list(map(name, n.split(", ")))
rates[v] = r
graph[v].update(n)
def part2(puzzle: str):
rates, graph = get_input(puzzle)
n = len(rates)
dist = {i: {j: 0 if i == j else 1 if j in graph[i] else 1e1337 for j in range(n)} for i in range(n)}
for k in range(n):
for i in range(n):
for j in range(n):
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
n = len(rates)
dist = {i: {j: 0 if i == j else 1 if j in graph[i] else 1e1337 for j in range(n)} for i in range(n)}
for k in range(n):
for i in range(n):
for j in range(n):
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
@cache
def solve(p, time, closed):
if time <= 0:
return 0
out = 0
for q in range(n):
if closed & 1 << q == 0:
continue
t = time - dist[p][q] - 1
out = max(out, solve(q, t, closed & ~(1 << q)) + rates[q] * t)
return out
@cache
def solve(p, time, closed):
if time <= 0:
return 0
out = 0
valves = [i for i in range(n) if rates[i]]
for s in range(1 << len(valves)):
a = solve(0, 26, reduce(lambda acc, x: acc | 1 << x, (j for i, j in enumerate(valves) if s & 1 << i), 0))
b = solve(0, 26, reduce(lambda acc, x: acc | 1 << x, (j for i, j in enumerate(valves) if s & 1 << i == 0), 0))
out = max(out, a + b)
for q in range(n):
if closed & 1 << q == 0:
continue
t = time - dist[p][q] - 1
out = max(out, solve(q, t, closed & ~(1 << q)) + rates[q] * t)
return out
if __name__ == "__main__":
from aoc import run
print(solve(0, 30, reduce(lambda acc, x: acc | 1 << x, (i for i in range(n) if rates[i]), 0)))
run(2022, 16, part1, part2)
solve.cache_clear()
out = 0
valves = [i for i in range(n) if rates[i]]
for s in range(1 << len(valves)):
a = solve(0, 26, reduce(lambda acc, x: acc | 1 << x, (j for i, j in enumerate(valves) if s & 1 << i), 0))
b = solve(0, 26, reduce(lambda acc, x: acc | 1 << x, (j for i, j in enumerate(valves) if s & 1 << i == 0), 0))
out = max(out, a + b)
print(out)

View file

@ -1,3 +1,7 @@
from lib import *
input = read_input(2022, 17).strip()
SHAPES = [
[[1, 1, 1, 1]],
[[0, 1, 0], [1, 1, 1], [0, 1, 0]],
@ -11,11 +15,7 @@ START_X = 2
START_Y = 3
def get_input(puzzle: str) -> str:
return puzzle
def part1(puzzle: str):
def part1():
grid = set()
height = 0
sh = 0
@ -38,8 +38,8 @@ def part1(puzzle: str):
x = START_X
y = height + START_Y + len(s) - 1
while True:
d = {"<": -1, ">": 1}[puzzle[st]]
st = (st + 1) % len(puzzle)
d = {"<": -1, ">": 1}[input[st]]
st = (st + 1) % len(input)
if test(s, x + d, y):
x += d
if test(s, x, y - 1):
@ -51,7 +51,7 @@ def part1(puzzle: str):
return height
def part2(puzzle: str):
def part2():
grid = set()
height = 0
st = 0
@ -87,8 +87,8 @@ def part2(puzzle: str):
x = START_X
y = height + START_Y + len(s) - 1
while True:
d = {"<": -1, ">": 1}[puzzle[st]]
st = (st + 1) % len(puzzle)
d = {"<": -1, ">": 1}[input[st]]
st = (st + 1) % len(input)
if test(s, x + d, y):
x += d
if test(s, x, y - 1):
@ -105,7 +105,5 @@ def part2(puzzle: str):
return height
if __name__ == "__main__":
from aoc import run
run(2022, 17, part1, part2)
print(part1())
print(part2())

View file

@ -1,92 +1,62 @@
from utils.parsing import ints
from lib import *
input = read_input(2022, 18)
def get_input(puzzle: str) -> list[tuple[int, int, int]]:
return [(x, y, z) for x, y, z in map(ints, puzzle.splitlines())]
cubes = [(x, y, z) for x, y, z in map(ints, input.splitlines())]
def part1(puzzle: str):
cubes = get_input(puzzle)
out = 0
for i, x in enumerate(cubes):
out += 6
for y in cubes[:i]:
if abs(x[0] - y[0]) + abs(x[1] - y[1]) + abs(x[2] - y[2]) == 1:
out -= 2
return out
out = 0
for i, x in enumerate(cubes):
out += 6
for y in cubes[:i]:
if abs(x[0] - y[0]) + abs(x[1] - y[1]) + abs(x[2] - y[2]) == 1:
out -= 2
print(out)
def part2(puzzle: str):
cubes = get_input(puzzle)
out = 0
(minx, miny, minz) = (maxx, maxy, maxz) = cubes[0]
for i, x in enumerate(cubes):
out += 6
for y in cubes[:i]:
if abs(x[0] - y[0]) + abs(x[1] - y[1]) + abs(x[2] - y[2]) == 1:
out -= 2
minx = min(x[0], minx)
miny = min(x[1], miny)
minz = min(x[2], minz)
maxx = max(x[0], maxx)
maxy = max(x[1], maxy)
maxz = max(x[2], maxz)
out = 0
(minx, miny, minz) = (maxx, maxy, maxz) = cubes[0]
for i, x in enumerate(cubes):
out += 6
for y in cubes[:i]:
if abs(x[0] - y[0]) + abs(x[1] - y[1]) + abs(x[2] - y[2]) == 1:
out -= 2
minx = min(x[0], minx)
miny = min(x[1], miny)
minz = min(x[2], minz)
maxx = max(x[0], maxx)
maxy = max(x[1], maxy)
maxz = max(x[2], maxz)
checked = set()
candidates = [
(i, j, k)
for x, y, z in cubes
for i, j, k in [
(x, y, z - 1),
(x, y, z + 1),
(x, y - 1, z),
(x, y + 1, z),
(x - 1, y, z),
(x + 1, y, z),
]
if (i, j, k) not in cubes
]
while candidates:
queue = [candidates.pop()]
if queue[0] in checked:
checked = set()
candidates = [
(i, j, k)
for x, y, z in cubes
for i, j, k in [(x, y, z - 1), (x, y, z + 1), (x, y - 1, z), (x, y + 1, z), (x - 1, y, z), (x + 1, y, z)]
if (i, j, k) not in cubes
]
while candidates:
queue = [candidates.pop()]
if queue[0] in checked:
continue
visited = set()
while queue:
x, y, z = queue.pop(0)
if x not in range(minx, maxx + 1) or y not in range(miny, maxy + 1) or z not in range(minz, maxz + 1):
break
if (x, y, z) in visited:
continue
visited = set()
while queue:
x, y, z = queue.pop(0)
if x not in range(minx, maxx + 1) or y not in range(miny, maxy + 1) or z not in range(minz, maxz + 1):
break
if (x, y, z) in visited:
continue
visited.add((x, y, z))
for q in [
(x, y, z - 1),
(x, y, z + 1),
(x, y - 1, z),
(x, y + 1, z),
(x - 1, y, z),
(x + 1, y, z),
]:
if q not in cubes and q not in visited:
queue.append(q)
else:
for x, y, z in visited:
for q in [
(x, y, z - 1),
(x, y, z + 1),
(x, y - 1, z),
(x, y + 1, z),
(x - 1, y, z),
(x + 1, y, z),
]:
if q in cubes:
out -= 1
visited.add((x, y, z))
for q in [(x, y, z - 1), (x, y, z + 1), (x, y - 1, z), (x, y + 1, z), (x - 1, y, z), (x + 1, y, z)]:
if q not in cubes and q not in visited:
queue.append(q)
else:
for x, y, z in visited:
for q in [(x, y, z - 1), (x, y, z + 1), (x, y - 1, z), (x, y + 1, z), (x - 1, y, z), (x + 1, y, z)]:
if q in cubes:
out -= 1
checked.update(visited)
checked.update(visited)
return out
if __name__ == "__main__":
from aoc import run
run(2022, 18, part1, part2)
print(out)

19
Python/justfile Normal file
View file

@ -0,0 +1,19 @@
alias r := run
alias t := test
alias ty := test-year
alias ta := test-all
_default:
@just --list
run year day:
python {{year}}/{{day}}.py
test year day:
@diff <(just run {{year}} {{day}}) <(cat ../.cache/{{year}}/{{trim_start_match(day, "0")}}.{1,2})
test-year year:
@set -e; for day in $(ls {{year}}); do just test {{year}} $(basename $day .py); done
test-all:
@set -e; for year in *; do [[ -d $year ]] || continue; just test-year $year; done

View file

@ -1,3 +1,39 @@
from .graph import *
from .grid import *
from .lists import *
from .math import *
from .parsing import *
del graph, grid, lists, math, parsing
import ast
import bisect
import collections
import functools
import graphlib
import heapq
import io
import itertools
import json
import math
import operator
import re
import statistics
from collections import Counter
from copy import deepcopy
from functools import cache, partial, reduce
from heapq import heapify, heappop, heappush
from pathlib import Path
import numpy as np
import pyperclip
def read_input(year: int, day: int) -> str:
f = Path(__file__).parent / f"../../.cache/{year}/{day}"
return f.read_text()
def ans(answer):
print(answer)
pyperclip.copy(str(answer))

View file

@ -1,33 +0,0 @@
import ast
import bisect
import collections
import functools
import graphlib
import heapq
import io
import itertools
import json
import math
import operator
import re
import statistics
import numpy as np
import pyperclip
from .graph import *
from .grid import *
from .list import *
from .math import *
from .parsing import *
heappush = heapq.heappush
heappop = heapq.heappop
reduce = functools.reduce
Counter = collections.Counter
def ans(answer):
print(answer)
pyperclip.copy(str(answer))

View file

@ -1,2 +1,2 @@
# AdventOfCode
[Advent of Code](https://adventofcode.com/) solutions in Python, Rust, Haskell and APL
[Advent of Code](https://adventofcode.com/) solutions in Rust, Haskell, Python and APL

55
aoc.py
View file

@ -1,55 +0,0 @@
from typing import Any, Callable
from pathlib import Path
SESSION = Path(__file__).parent.joinpath(".session.txt").read_text().strip()
def run(
year: int,
day: int,
part1: Callable[[str], Any],
part2: Callable[[str], Any],
strip: str | bool = "\n",
f: Path | None = None,
):
puzzle = load(year, day, strip, f)
print("part 1:", part1(puzzle))
print("part 2:", part2(puzzle))
def load(year: int, day: int, strip: str | bool = "\n", f: Path | None = None) -> str:
if not f:
f = Path(__file__).parent / f"{year}/{day:02}.txt"
if not f.exists():
puzzle = _fetch_input(year, day)
if puzzle is None:
raise Exception("Puzzle input could not be fetched!")
f.write_text(puzzle)
else:
puzzle = f.read_text()
if strip:
puzzle = puzzle.strip() if strip is True else puzzle.strip(strip)
return puzzle
def setup(year: int, day: int, strip: str | bool = "\n", f: str | None = None) -> str:
return load(year, day, strip, Path(f) if isinstance(f, str) else f)
def create_file(path: str, content: str, debug: bool = False) -> None:
if debug:
print(f"Creating file {path}")
with open(path, "w") as f:
f.write(content)
f.flush()
def _fetch_input(year: int, day: int, debug: bool = False) -> str | None:
import requests
if debug:
print(f"Fetching input for {year}/{day:02}")
response = requests.get(f"https://adventofcode.com/{year}/day/{day}/input", cookies={"session": SESSION})
return response.text if response.ok else None

View file

@ -28,14 +28,23 @@ with import <nixpkgs> {}; let
in
mkShell {
buildInputs = [
just
downloadInput
getSession
];
packages = [
just
# Python
(python311.withPackages (p:
with p; [
numpy
pyperclip
]))
# Haskell
(haskellPackages.ghcWithPackages (p: with p; [regex-tdfa]))
haskell-language-server
ormolu # haskell code formatter
];
PYTHONPATH = ".";
}