AoC 2023 Advent of Code (Python)

By telleropnul, December 31, 2023

Advent of Code (AoC) is an Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like. Go check it out: Advent of Code

Input files: adventofcode2023inputs.zip

2023 Day 25

python -m pip install -U networkx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from math import prod
 
import networkx as nx
 
G = nx.Graph()
 
with open("input") as f:
    for line in f:
        v, adj = line.split(": ")
        for a in adj.strip().split(" "):
            G.add_edge(v, a)
 
G.remove_edges_from(nx.minimum_edge_cut(G))
 
print(prod([len(c) for c in nx.connected_components(G)]))

2023 Day 24 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sympy as sp
X, Y, Z = 0, 1, 2
 
axr,ayr,azr = sp.symbols('axr,ayr,azr')
bzr,byr,bxr= sp.symbols('bzr,byr,bxr', positive=True, integer=True)
 
def expand_system(pos, speed, system):
    system.append(sp.Eq((pos[X] - bxr) * (ayr - speed[Y]), (pos[Y] - byr) * (axr-speed[X])))
    system.append(sp.Eq((pos[X] - bxr) * (azr - speed[Z]), (pos[Z] - bzr) * (axr-speed[X])))
 
linear_system = []
for line in open("input"):
    pos, speed = line.strip().split('@')
    expand_system(list(map(int, pos.split(','))), list(map(int, speed.split(','))), linear_system)
 
ans = sp.solve(linear_system, [azr,bzr,ayr,byr,axr,bxr])
print(ans)
print(ans[0][1]+ ans[0][3]+ans[0][5])

2023 Day 24 Part 01

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
import sympy as sp
X, Y, Z = 0, 1, 2
UPPER_LIMIT = 400000000000000
LOWER_LIMIT = 200000000000000
 
Y_PER_X, Y_AT_X0, SPEED_Y, POS_Y = 0, 1, 2, 3
 
# process input
data = []
for line in open("input"):
    pos, speed = line.strip().split('@')
    pos = [int(pos) for pos in pos.split(',')]
    speed = [int(speed) for speed in speed.split(',')]
    data.append((pos, speed))
 
#process data
lines = []
for pos, speed in data:
    y_per_x = speed[Y] / speed[X]
    y_at_x0 = pos[Y] - y_per_x * pos[X]
    t_per_x = 1 / speed[X]
    t_at_x0 = 0 - t_per_x * pos[X]
    lines.append((y_per_x, y_at_x0, speed[Y], pos[Y]))
 
#intersection
def intersect(A, B):
    if (B[Y_PER_X] - A[Y_PER_X]) == 0:
        return False
 
    x_val = (A[Y_AT_X0]-B[Y_AT_X0]) / (B[Y_PER_X] - A[Y_PER_X])
    y_val = x_val * A[Y_PER_X] + A[Y_AT_X0]
 
    if LOWER_LIMIT <= x_val <= UPPER_LIMIT and LOWER_LIMIT <= y_val <= UPPER_LIMIT:
        if ((A[SPEED_Y] > 0 and y_val > A[POS_Y]) or (A[SPEED_Y] < 0 and y_val < A[POS_Y])) \
        and ((B[SPEED_Y] > 0 and y_val > B[POS_Y]) or (B[SPEED_Y] < 0 and y_val < B[POS_Y])):
            return True
    return False
 
# run solution
counter = 0
for index, line1 in enumerate(lines):
    for line2 in lines[index+1:]:
        if intersect(line1, line2):
            counter += 1
print(counter)

2023 Day 23 Part 02

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
import sys
 
sys.setrecursionlimit(2**31 - 1)
 
 
def neighbors(i, j):
    yield i - 1, j
    yield i + 1, j
    yield i, j - 1
    yield i, j + 1
 
 
data = open('input').read().splitlines()
N, M = len(data), len(data[0])
grid = {(i, j) for i, l in enumerate(data) for j, x in enumerate(l) if x != "#"}
adj = {p: {q: 1 for q in neighbors(*p) if q in grid} for p in grid}
 
while True:
    for p, qs in adj.items():
        if len(qs) != 2:
            continue
        q1, q2 = adj[p]
        adj[q1][q2] = adj[q2][q1] = adj[q1][p] + adj[p][q2]
        del adj[p], adj[q1][p], adj[q2][p]
        break
    else:
        break
 
visited = set()
 
def dfs(p):
    if p == (N - 1, M - 2):
        return 0
    visited.add(p)
    ans = float("-inf")
    for n, dist in adj[p].items():
        if n in visited:
            continue
        ans = max(ans, dist + dfs(n))
    visited.remove(p)
    return ans
 
print(dfs((0, 1)))

2023 Day 23 Part 01

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
import sys
 
sys.setrecursionlimit(2**31 - 1)
 
 
def neighbors(i, j):
    yield i - 1, j
    yield i + 1, j
    yield i, j - 1
    yield i, j + 1
 
 
data = open('input').read().splitlines()
N, M = len(data), len(data[0])
grid = {(i, j): x for i, l in enumerate(data) for j, x in enumerate(l) if x != "#"}
 
visited = set()
 
def dfs(p):
    if p == (N - 1, M - 2):
        return 0
    visited.add(p)
    ans = float("-inf")
    ns = list(neighbors(*p))
    if grid[p] in "^v<>":
        ns = [ns["^v<>".index(grid[p])]]
    for n in ns:
        if n not in grid or n in visited:
            continue
        ans = max(ans, 1 + dfs(n))
    visited.remove(p)
    return ans
 
print(dfs((0, 1)))

2023 Day 22 Part 02

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
from itertools import zip_longest
 
 
def irange(start, stop):
    if start <= stop:
        return range(start, stop + 1)
    else:
        return range(start, stop - 1, -1)
 
 
def draw(a, b):
    i1, j1, k1 = a
    i2, j2, k2 = b
    result = zip_longest(irange(i1, i2), irange(j1, j2), irange(k1, k2))
    return set((a or i1, b or j1, c or k1) for a, b, c in result)
 
 
def fall(tiles, brick):
    while True:
        down = {(x, y, z - 1) for x, y, z in brick}
        if any(z == 0 for _, _, z in down) or down & tiles:
            return brick
        brick = down
 
 
tiles = set()
bricks = []
 
data = open('input').read().splitlines()
for line in data:
    a, b = [[int(x) for x in r.split(",")] for r in line.split("~")]
    brick = draw(a, b)
    bricks.append(brick)
    tiles |= brick
 
bricks.sort(key=lambda p: min(z for _, _, z in p))
 
for i, brick in enumerate(bricks):
    tiles -= brick
    bricks[i] = fall(tiles, brick)
    tiles |= bricks[i]
 
ans = 0
for brick in bricks:
    without = tiles - brick
    for other in bricks:
        if other == brick:
            continue
        without -= other
        if fall(without, other) != other:
            ans += 1
        else:
            without |= other
print(ans)

2023 Day 22 Part 01

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
from itertools import zip_longest
 
 
def irange(start, stop):
    if start <= stop:
        return range(start, stop + 1)
    else:
        return range(start, stop - 1, -1)
 
 
def draw(a, b):
    i1, j1, k1 = a
    i2, j2, k2 = b
    result = zip_longest(irange(i1, i2), irange(j1, j2), irange(k1, k2))
    return set((a or i1, b or j1, c or k1) for a, b, c in result)
 
 
def fall(tiles, brick):
    while True:
        down = {(x, y, z - 1) for x, y, z in brick}
        if any(z == 0 for _, _, z in down) or down & tiles:
            return brick
        brick = down
 
 
tiles = set()
bricks = []
 
data = open('input').read().splitlines()
for line in data:
    a, b = [[int(x) for x in r.split(",")] for r in line.split("~")]
    brick = draw(a, b)
    bricks.append(brick)
    tiles |= brick
 
bricks.sort(key=lambda p: min(z for _, _, z in p))
 
for i, brick in enumerate(bricks):
    tiles -= brick
    bricks[i] = fall(tiles, brick)
    tiles |= bricks[i]
 
ans = 0
for brick in bricks:
    without = tiles - brick
    for other in bricks:
        if other == brick:
            continue
        without -= other
        if fall(without, other) != other:
            break
        without |= other
    else:
        ans += 1
print(ans)

2023 Day 21 Part 01 + 02

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
from collections import deque
import numpy as np
 
 
def zxcv(fname, n):
    dat1 = open('input').read().splitlines()
 
    if n > 64:
        data = []
        for i in range(5):
            for line in dat1:
                data.append(5 * line.replace("S", "."))
    else:
        data = dat1
 
    width = len(data[0])
    height = len(data)
 
    q = deque()
 
    sx, sy = width // 2, height // 2
 
    q.append((sx, sy, 0))
    s64 = set()
    visited = set()
 
    while q:
        x, y, steps = q.popleft()
        if (x, y, steps) in visited:
            continue
        visited.add((x, y, steps))
        if steps == n:
            s64.add((x, y))
        else:
            if x >= 0:
                if data[y][x - 1] != "#":
                    q.append((x - 1, y, steps + 1))
            if x < width - 1:
                if data[y][x + 1] != "#":
                    q.append((x + 1, y, steps + 1))
            if y >= 0:
                if data[y - 1][x] != "#":
                    q.append((x, y - 1, steps + 1))
            if y < height - 1:
                if data[y + 1][x] != "#":
                    q.append((x, y + 1, steps + 1))
 
    if n == 64:
        for y, line in enumerate(data):
            ll = list(line)
            for sx, sy in s64:
                if sy == y:
                    ll[sx] = "O"
            print("".join(ll))
 
    return len(s64)
 
 
zxcv("input", 2)
print("part 1:", zxcv("input", 64))
 
# polynomial extrapolation
a0 = zxcv("input", 65)
a1 = zxcv("input", 65 + 131)
a2 = zxcv("input", 65 + 2 * 131)
 
vandermonde = np.matrix([[0, 0, 1], [1, 1, 1], [4, 2, 1]])
b = np.array([a0, a1, a2])
x = np.linalg.solve(vandermonde, b).astype(np.int64)
 
# note that 26501365 = 202300 * 131 + 65 where 131 is the dimension of the grid
n = 202300
print("part 2:", x[0] * n * n + x[1] * n + x[2])

2023 Day 21 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def do(get, start, t):
    bfs = set([start])
 
    for _ in range(t):
        new_bfs = set()
        for i, j in bfs:
            new_bfs.add((i, j - 1))
            new_bfs.add((i, j + 1))
            new_bfs.add((i - 1, j))
            new_bfs.add((i + 1, j))
        new_bfs = {(i, j) for i, j in new_bfs if get(i, j) == "."}
        bfs = new_bfs
 
    return len(bfs)
 
 
data = open('input').read().splitlines()
grid = {(i, j): x for i, line in enumerate(data) for j, x in enumerate(line.strip())}
start = next(p for p, x in grid.items() if x == "S")
grid[start] = "."
print(do(lambda i, j: grid.get((i, j)), start, 64))

2023 Day 20 Part 02

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
import math
from collections import deque
 
class Module:
    def __init__(self, name, type, outputs):
        self.name = name
        self.type = type
        self.outputs = outputs
 
        if type == "%":
            self.memory = "off"
        else:
            self.memory = {}
    def __repr__(self):
        return self.name + "{type=" + self.type + ",outputs=" + ",".join(self.outputs) + ",memory=" + str(self.memory) + "}"
 
modules = {}
broadcast_targets = []
 
data = open('input').read().splitlines()
for line in data:
    left, right = line.strip().split(" -> ")
    outputs = right.split(", ")
    if left == "broadcaster":
        broadcast_targets = outputs
    else:
        type = left[0]
        name = left[1:]
        modules[name] = Module(name, type, outputs)
 
for name, module in modules.items():
    for output in module.outputs:
        if output in modules and modules[output].type == "&":
            modules[output].memory[name] = "lo"
 
(feed,) = [name for name, module in modules.items() if "rx" in module.outputs]
 
cycle_lengths = {}
seen = {name: 0 for name, module in modules.items() if feed in module.outputs}
 
presses = 0
 
while True:
    presses += 1
    q = deque([("broadcaster", x, "lo") for x in broadcast_targets])
 
    while q:
        origin, target, pulse = q.popleft()
 
        if target not in modules:
            continue
 
        module = modules[target]
 
        if module.name == feed and pulse == "hi":
            seen[origin] += 1
 
            if origin not in cycle_lengths:
                cycle_lengths[origin] = presses
            else:
                assert presses == seen[origin] * cycle_lengths[origin]
 
            if all(seen.values()):
                x = 1
                for cycle_length in cycle_lengths.values():
                    x = x * cycle_length // math.gcd(x, cycle_length)
                print(x)
                exit(0)
 
        if module.type == "%":
            if pulse == "lo":
                module.memory = "on" if module.memory == "off" else "off"
                outgoing = "hi" if module.memory == "on" else "lo"
                for x in module.outputs:
                    q.append((module.name, x, outgoing))
        else:
            module.memory[origin] = pulse
            outgoing = "lo" if all(x == "hi" for x in module.memory.values()) else "hi"
            for x in module.outputs:
                q.append((module.name, x, outgoing))

2023 Day 20 Part 01

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
from collections import deque
 
class Module:
    def __init__(self, name, type, outputs):
        self.name = name
        self.type = type
        self.outputs = outputs
 
        if type == "%":
            self.memory = "off"
        else:
            self.memory = {}
    def __repr__(self):
        return self.name + "{type=" + self.type + ",outputs=" + ",".join(self.outputs) + ",memory=" + str(self.memory) + "}"
 
modules = {}
broadcast_targets = []
 
data = open('input').read().splitlines()
for line in data:
    left, right = line.strip().split(" -> ")
    outputs = right.split(", ")
    if left == "broadcaster":
        broadcast_targets = outputs
    else:
        type = left[0]
        name = left[1:]
        modules[name] = Module(name, type, outputs)
 
for name, module in modules.items():
    for output in module.outputs:
        if output in modules and modules[output].type == "&":
            modules[output].memory[name] = "lo"
 
lo = hi = 0
 
for _ in range(1000):
    lo += 1
    q = deque([("broadcaster", x, "lo") for x in broadcast_targets])
 
    while q:
        origin, target, pulse = q.popleft()
 
        if pulse == "lo":
            lo += 1
        else:
            hi += 1
 
        if target not in modules:
            continue
 
        module = modules[target]
 
        if module.type == "%":
            if pulse == "lo":
                module.memory = "on" if module.memory == "off" else "off"
                outgoing = "hi" if module.memory == "on" else "lo"
                for x in module.outputs:
                    q.append((module.name, x, outgoing))
        else:
            module.memory[origin] = pulse
            outgoing = "lo" if all(x == "hi" for x in module.memory.values()) else "hi"
            for x in module.outputs:
                q.append((module.name, x, outgoing))
 
print(lo * hi)

2023 Day 19 Part 02

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
from collections import deque
 
class Thing:
    def __init__(self, name, range=None):
        self.name = name
        self.range = range
 
    def __lt__(self, other):
        return Thing(self.name, range(1, other))
 
    def __gt__(self, other):
        return Thing(self.name, range(other + 1, 4001))
 
 
def split_range(a, b):
    sect = range(max(a.start, b.start), min(a.stop, b.stop))
    left = range(a.start, sect.start)
    right = range(sect.stop, a.stop)
    return left, sect, right
 
 
data = open('input').read().split("\n\n")
workflows, _ = data
names = {"A": "A", "R": "R"}
for line in workflows.splitlines():
    name, conds = line.split("{")
    names[name] = name
 
maps = {}
for line in workflows.splitlines():
    name, conds = line.split("{")
    x, m, a, s = Thing("x"), Thing("m"), Thing("a"), Thing("s")
    conds = "{" + ",None:".join(conds.rsplit(",", 1))
    maps[name] = eval(conds, globals(), names)
 
q = deque()
q.append(("in", *[range(1, 4001) for _ in range(4)]))
ans = 0
 
while q:
    at, x, m, a, s = q.popleft()
    if at == "R":
        continue
    if at == "A":
        ans += len(x) * len(m) * len(a) * len(s)
        continue
    for k, v in maps[at].items():
        if k is None:
            q.append((v, x, m, a, s))
            continue
        left, sect, right = split_range(eval(k.name), k.range)
        assert not (left and right)
        exec(f"global {k.name}; {k.name} = sect")
        q.append((v, x, m, a, s))
        exec(f"global {k.name}; {k.name} = left or right")
print(ans)

2023 Day 19 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from collections import deque
 
data = open('input').read().split("\n\n")
workflows, instructions = data
names = {"A": "A", "R": "R"}
maps = {}
for line in workflows.splitlines():
    name, conds = line[:-1].split("{")
    conds = conds.replace(":", " and ").replace(",", " or ")
    maps[name] = conds
    names[name] = name
ans = 0
for inst in instructions.splitlines():
    x, m, a, s = [int(x[2:]) for x in inst[1:-1].split(",")]
    at = "in"
    while at not in "AR":
        at = eval(maps[at], names, locals())
    if at == "A":
        ans += x + m + a + s
print(ans)

2023 Day 18 Part 02

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
class _(tuple):
    def __add__(self, other):
        return _(x + y for x, y in zip(self, other))
 
    def __mul__(self, other):
        return _(x * other for x in self)
 
 
NORTH = _((-1, 0))
SOUTH = _((1, 0))
EAST = _((0, 1))
WEST = _((0, -1))
 
DIRECTIONS = {
    "U": NORTH,
    "D": SOUTH,
    "R": EAST,
    "L": WEST,
}
 
pos = _((0, 0))
vertices = [pos]
boundary = 0
data = open('input').read().splitlines()
for line in data:
    dir, num, hex = line.split()
    dir = "RDLU"[int(hex[-2])]
    num = int(hex[2:-2], 16)
    vertices.append(vertices[-1] + DIRECTIONS[dir] * num)
    boundary += num
xs, ys = zip(*vertices)
left = sum(a * b for a, b in zip(xs, ys[1:]))
right = sum(a * b for a, b in zip(ys, xs[1:]))
print (abs(left - right) // 2 + boundary // 2 + 1)

2023 Day 18 Part 01

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
class _(tuple):
    def __add__(self, other):
        return _(x + y for x, y in zip(self, other))
 
    def __mul__(self, other):
        return _(x * other for x in self)
 
 
NORTH = _((-1, 0))
SOUTH = _((1, 0))
EAST = _((0, 1))
WEST = _((0, -1))
 
DIRECTIONS = {
    "U": NORTH,
    "D": SOUTH,
    "R": EAST,
    "L": WEST,
}
 
pos = _((0, 0))
vertices = [pos]
boundary = 0
data = open('input').read().splitlines()
for line in data:
    dir, num, hex = line.split()
    num = int(num)
    vertices.append(vertices[-1] + DIRECTIONS[dir] * num)
    boundary += num
xs, ys = zip(*vertices)
left = sum(a * b for a, b in zip(xs, ys[1:]))
right = sum(a * b for a, b in zip(ys, xs[1:]))
print (abs(left - right) // 2 + boundary // 2 + 1)

2023 Day 17 Part 02

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
from collections import defaultdict
from heapq import heappop, heappush
 
 
def dijkstra(grid, s, e, neighbors, max_run):
    start = s, (0, 0)
    dist = defaultdict(lambda: float("inf"), {start: 0})
    pq = [(0, start)]
    while pq:
        _, u = heappop(pq)
        if not all(-max_run <= n <= max_run for n in u[1]):
            continue
        if u[0] == e:
            return dist[u]
        for v in neighbors(*u):
            if v[0] not in grid:
                continue
            alt = dist[u] + grid[v[0]]
            if alt < dist[v]:
                dist[v] = alt
                heappush(pq, (alt, v))
 
 
def neighbors1(p, run):
    r, c = p
    nr, nc = run
    if nr == 0:
        yield (r - 1, c), (-1, 0)
        yield (r + 1, c), (1, 0)
    if nc == 0:
        yield (r, c - 1), (0, -1)
        yield (r, c + 1), (0, 1)
    if nr > 0:
        yield (r + 1, c), (nr + 1, 0)
    if nr < 0:
        yield (r - 1, c), (nr - 1, 0)
    if nc > 0:
        yield (r, c + 1), (0, nc + 1)
    if nc < 0:
        yield (r, c - 1), (0, nc - 1)
 
 
def neighbors2(p, run):
    r, c = p
    nr, nc = run
    if 0 < nr < 4:
        yield (r + 1, c), (nr + 1, 0)
    elif -4 < nr < 0:
        yield (r - 1, c), (nr - 1, 0)
    elif 0 < nc < 4:
        yield (r, c + 1), (0, nc + 1)
    elif -4 < nc < 0:
        yield (r, c - 1), (0, nc - 1)
    else:
        yield from neighbors1(p, run)
 
 
data = open('input').read().splitlines()
grid = {(i, j): int(x) for i, line in enumerate(data) for j, x in enumerate(line)}
n, m = len(data), len(data[0])
print (dijkstra(grid, (0, 0), (n - 1, m - 1), neighbors2, 10))

2023 Day 17 Part 01

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
from collections import defaultdict
from heapq import heappop, heappush
 
 
def dijkstra(grid, s, e, neighbors, max_run):
    start = s, (0, 0)
    dist = defaultdict(lambda: float("inf"), {start: 0})
    pq = [(0, start)]
    while pq:
        _, u = heappop(pq)
        if not all(-max_run <= n <= max_run for n in u[1]):
            continue
        if u[0] == e:
            return dist[u]
        for v in neighbors(*u):
            if v[0] not in grid:
                continue
            alt = dist[u] + grid[v[0]]
            if alt < dist[v]:
                dist[v] = alt
                heappush(pq, (alt, v))
 
 
def neighbors1(p, run):
    r, c = p
    nr, nc = run
    if nr == 0:
        yield (r - 1, c), (-1, 0)
        yield (r + 1, c), (1, 0)
    if nc == 0:
        yield (r, c - 1), (0, -1)
        yield (r, c + 1), (0, 1)
    if nr > 0:
        yield (r + 1, c), (nr + 1, 0)
    if nr < 0:
        yield (r - 1, c), (nr - 1, 0)
    if nc > 0:
        yield (r, c + 1), (0, nc + 1)
    if nc < 0:
        yield (r, c - 1), (0, nc - 1)
 
data = open('input').read().splitlines()
grid = {(i, j): int(x) for i, line in enumerate(data) for j, x in enumerate(line)}
n, m = len(data), len(data[0])
print(dijkstra(grid, (0, 0), (n - 1, m - 1), neighbors1, 3))

2023 Day 16 Part 02

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
from collections import deque
 
 
class _(tuple):
    def __add__(self, other):
        return _(x + y for x, y in zip(self, other))
 
 
NORTH = _((-1, 0))
SOUTH = _((1, 0))
EAST = _((0, 1))
WEST = _((0, -1))
 
 
def do(grid, start, dstart):
    q = deque([(_(start), _(dstart))])
    visited = set()
 
    while q:
        i, di = q.popleft()
        if i not in grid or (i, di) in visited:
            continue
        visited.add((i, di))
        match grid[i]:
            case "/":
                di = -di[1], -di[0]
                q.append((i + di, di))
            case "\\":
                di = di[1], di[0]
                q.append((i + di, di))
            case "|" if di[0] == 0:
                q.append((i + NORTH, NORTH))
                q.append((i + SOUTH, SOUTH))
            case "-" if di[1] == 0:
                q.append((i + EAST, EAST))
                q.append((i + WEST, WEST))
            case _:
                q.append((i + di, di))
 
    return len(set(p for p, _ in visited))
 
data = open('input').read().splitlines()
grid = {(i, j): x for i, row in enumerate(data) for j, x in enumerate(row)}
n, m = len(data), len(data[0])
ans = 0
for i in range(n):
    ans = max(ans, do(grid, (0, i), SOUTH))
    ans = max(ans, do(grid, (n - 1, i), NORTH))
for i in range(m):
    ans = max(ans, do(grid, (i, 0), EAST))
    ans = max(ans, do(grid, (i, m - 1), WEST))
print(ans)

2023 Day 16 Part 01

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
from collections import deque
 
 
class _(tuple):
    def __add__(self, other):
        return _(x + y for x, y in zip(self, other))
 
 
NORTH = _((-1, 0))
SOUTH = _((1, 0))
EAST = _((0, 1))
WEST = _((0, -1))
 
 
def do(grid, start, dstart):
    q = deque([(_(start), _(dstart))])
    visited = set()
 
    while q:
        i, di = q.popleft()
        if i not in grid or (i, di) in visited:
            continue
        visited.add((i, di))
        match grid[i]:
            case "/":
                di = -di[1], -di[0]
                q.append((i + di, di))
            case "\\":
                di = di[1], di[0]
                q.append((i + di, di))
            case "|" if di[0] == 0:
                q.append((i + NORTH, NORTH))
                q.append((i + SOUTH, SOUTH))
            case "-" if di[1] == 0:
                q.append((i + EAST, EAST))
                q.append((i + WEST, WEST))
            case _:
                q.append((i + di, di))
 
    return len(set(p for p, _ in visited))
 
data = open('input').read().splitlines()
grid = {(i, j): x for i, row in enumerate(data) for j, x in enumerate(row.strip())}
print(do(grid, (0, 0), EAST))

2023 Day 15 Part 02

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
from collections import defaultdict
 
def hash(x):
    val = 0
    for c in x:
        val += ord(c)
        val *= 17
        val %= 256
    return val
 
hashmap = defaultdict(dict)
data = open('input').read().strip().split(",")
for block in data:
    if "=" in block:
        label, block = block.split("=")
        hashmap[hash(label)][label] = int(block)
    elif "-" in block:
        label = block[:-1]
        hashmap[hash(label)].pop(label, None)
 
print(sum(
    (1 + i) * (1 + j) * val
    for i, box in hashmap.items()
    for j, val in enumerate(box.values())
))

2023 Day 15 Part 01

1
2
3
4
5
6
7
8
9
10
def hash(x):
    val = 0
    for c in x:
        val += ord(c)
        val *= 17
        val %= 256
    return val
 
data = open('input').read().strip().split(",")
print(sum(map(hash, data)))

2023 Day 14 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
grid = tuple(open('input').read().splitlines())
 
def cycle():
    global grid
    for _ in range(4):
        grid = tuple(map("".join, zip(*grid)))
        grid = tuple("#".join(["".join(sorted(tuple(group), reverse=True)) for group in row.split("#")]) for row in grid)
        grid = tuple(row[::-1] for row in grid)
 
seen = {grid}
array = [grid]
iter = 0
while True:
    iter += 1
    cycle()
    if grid in seen:
        break
    seen.add(grid)
    array.append(grid)
first = array.index(grid)
grid = array[(1000000000 - first) % (iter - first) + first]
print(sum(row.count("O") * (len(grid) - r) for r, row in enumerate(grid)))

2023 Day 14 Part 01

1
2
3
4
5
grid = open('input').read().splitlines()
grid = list(map("".join, zip(*grid)))
grid = ["#".join(["".join(sorted(list(group), reverse=True)) for group in row.split("#")]) for row in grid]
grid = list(map("".join, zip(*grid)))
print(sum(row.count("O") * (len(grid) - r) for r, row in enumerate(grid)))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
data = open('input').read().splitlines()
grid = list ([[x for x in line] for line in data])
 
while True:
    modified = False
    for y in range (0,len(grid[0])):
        for x in range (0,len(grid)-1):
            if grid[x][y] == "." and grid[x+1][y] =="O":
                # a,b = b,a
                grid[x][y], grid[x+1][y] = grid[x+1][y], grid[x][y]
                modified = True
    if modified == False:
        break
 
ans = 0
for x in range (0,len(grid)):
    count = 0
    for y in range (0,len(grid[0])):
        if grid[x][y] == 'O':
            count += 1
    ans += (len(grid) - x) * count
print(ans)

2023 Day 13 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def find(lines):
    for i in range(1, len(lines)):
        differs = sum(
            ax != bx
            for a, b in zip(lines[:i][::-1], lines[i:])
            for ax, bx in zip(a, b)
        )
        if differs == 1:
            return i
 
data = open('input').read().split("\n\n")
ans = 0
for block in data:
    block = block.splitlines()
    if v := find([*zip(*block)]):
        ans += v
    if h := find(block):
        ans += h * 100
print(ans)

2023 Day 13 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def find(lines):
    for i in range(1, len(lines)):
        if all(a == b for a, b in zip(lines[:i][::-1], lines[i:])):
            return i
 
 
data = open('input').read().split("\n\n")
ans = 0
for block in data:
    block = block.splitlines()
    if v := find([*zip(*block)]):
        ans += v
    if h := find(block):
        ans += h * 100
print(ans)

2023 Day 12 Part 02

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
from functools import cache
from itertools import combinations
 
@cache
def dp(chars, nums, *, curr=None):
    if len(nums) == 0 and curr is None:
        return "#" not in chars
 
    match chars[:1], curr:
        case "", None | 0:
            return len(nums) == 0
        case "", _:
            return 0
 
        case "?", None:
            return dp(chars[1:], nums) + dp(chars, nums[1:], curr=nums[0])
        case "?", 0:
            return dp(chars[1:], nums)
        case "?", _:
            return dp(chars[1:], nums, curr=curr - 1)
 
        case "#", None:
            return dp(chars, nums[1:], curr=nums[0])
        case "#", 0:
            return 0
        case "#", _:
            return dp(chars[1:], nums, curr=curr - 1)
 
        case ".", None | 0:
            return dp(chars[1:], nums)
        case ".", _:
            return 0
 
data = open('input').read().splitlines()
ans = 0
for line in data:
    chars, nums = line.split()
    chars = "?".join([chars] * 5)
    nums = tuple(int(x) for x in nums.split(",")) * 5
    ans += dp(chars, nums)
print(ans)

2023 Day 12 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def count(cfg, nums):
    if cfg == "":
        return 1 if nums == () else 0
    if nums == ():
        return 0 if "#" in cfg else 1
    result = 0
    if cfg[0] in ".?":
        result += count(cfg[1:], nums)
    if cfg[0] in "#?":
        if nums[0] <= len(cfg) and "." not in cfg[:nums[0]] and (nums[0] == len(cfg) or cfg[nums[0]] != "#"):
            result += count(cfg[nums[0] + 1:], nums[1:])
    return result
 
data = open('input').read().splitlines()
total = 0
for line in data:
    cfg, nums = line.split()
    nums = tuple(map(int, nums.split(",")))
    total += count(cfg, nums)
print(total)

2023 Day 11 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from collections import deque
 
grid = open('input').read().splitlines()
 
empty_rows = [r for r, row in enumerate(grid) if all(ch == "." for ch in row)]
empty_cols = [c for c, col in enumerate(zip(*grid)) if all(ch == "." for ch in col)]
 
points = [(r, c) for r, row in enumerate(grid) for c, ch in enumerate(row) if ch == "#"]
 
total = 0
scale = 1000000
 
for i, (r1, c1) in enumerate(points):
    for (r2, c2) in points[:i]:
        for r in range(min(r1, r2), max(r1, r2)):
            total += scale if r in empty_rows else 1
        for c in range(min(c1, c2), max(c1, c2)):
            total += scale if c in empty_cols else 1
 
print(total)

2023 Day 11 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from collections import deque
 
grid = open('input').read().splitlines()
 
empty_rows = [r for r, row in enumerate(grid) if all(ch == "." for ch in row)]
empty_cols = [c for c, col in enumerate(zip(*grid)) if all(ch == "." for ch in col)]
 
points = [(r, c) for r, row in enumerate(grid) for c, ch in enumerate(row) if ch == "#"]
 
total = 0
scale = 2
 
for i, (r1, c1) in enumerate(points):
    for (r2, c2) in points[:i]:
        for r in range(min(r1, r2), max(r1, r2)):
            total += scale if r in empty_rows else 1
        for c in range(min(c1, c2), max(c1, c2)):
            total += scale if c in empty_cols else 1
 
print(total)

2023 Day 10 Part 02

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
from collections import deque
 
grid = open('input').read().splitlines()
 
for r, row in enumerate(grid):
    for c, ch in enumerate(row):
        if ch == "S":
            sr = r
            sc = c
            break
    else:
        continue
    break
 
loop = {(sr, sc)}
q = deque([(sr, sc)])
 
maybe_s = {"|", "-", "J", "L", "7", "F"}
 
while q:
    r, c = q.popleft()
    ch = grid[r][c]
 
    if r > 0 and ch in "S|JL" and grid[r - 1][c] in "|7F" and (r - 1, c) not in loop:
        loop.add((r - 1, c))
        q.append((r - 1, c))
        if ch == "S":
            maybe_s &= {"|", "J", "L"}
 
    if r < len(grid) - 1 and ch in "S|7F" and grid[r + 1][c] in "|JL" and (r + 1, c) not in loop:
        loop.add((r + 1, c))
        q.append((r + 1, c))
        if ch == "S":
            maybe_s &= {"|", "7", "F"}
 
    if c > 0 and ch in "S-J7" and grid[r][c - 1] in "-LF" and (r, c - 1) not in loop:
        loop.add((r, c - 1))
        q.append((r, c - 1))
        if ch == "S":
            maybe_s &= {"-", "J", "7"}
 
    if c < len(grid[r]) - 1 and ch in "S-LF" and grid[r][c + 1] in "-J7" and (r, c + 1) not in loop:
        loop.add((r, c + 1))
        q.append((r, c + 1))
        if ch == "S":
            maybe_s &= {"-", "L", "F"}
 
assert len(maybe_s) == 1
(S,) = maybe_s
 
grid = [row.replace("S", S) for row in grid]
grid = ["".join(ch if (r, c) in loop else "." for c, ch in enumerate(row)) for r, row in enumerate(grid)]
 
outside = set()
 
for r, row in enumerate(grid):
    within = False
    up = None
    for c, ch in enumerate(row):
        if ch == "|":
            assert up is None
            within = not within
        elif ch == "-":
            assert up is not None
        elif ch in "LF":
            assert up is None
            up = ch == "L"
        elif ch in "7J":
            assert up is not None
            if ch != ("J" if up else "7"):
                within = not within
            up = None
        elif ch == ".":
            pass
        else:
            raise RuntimeError(f"unexpected character (horizontal): {ch}")
        if not within:
            outside.add((r, c))
 
print(len(grid) * len(grid[0]) - len(outside | loop))

2023 Day 10 Part 01

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
from collections import deque
 
grid = open('input').read().splitlines()
 
for r, row in enumerate(grid):
    for c, ch in enumerate(row):
        if ch == "S":
            sr = r
            sc = c
            break
    else:
        continue
    break
 
loop = {(sr, sc)}
q = deque([(sr, sc)])
 
while q:
    r, c = q.popleft()
    ch = grid[r][c]
 
    if r > 0 and ch in "S|JL" and grid[r - 1][c] in "|7F" and (r - 1, c) not in loop:
        loop.add((r - 1, c))
        q.append((r - 1, c))
 
    if r < len(grid) - 1 and ch in "S|7F" and grid[r + 1][c] in "|JL" and (r + 1, c) not in loop:
        loop.add((r + 1, c))
        q.append((r + 1, c))
 
    if c > 0 and ch in "S-J7" and grid[r][c - 1] in "-LF" and (r, c - 1) not in loop:
        loop.add((r, c - 1))
        q.append((r, c - 1))
 
    if c < len(grid[r]) - 1 and ch in "S-LF" and grid[r][c + 1] in "-J7" and (r, c + 1) not in loop:
        loop.add((r, c + 1))
        q.append((r, c + 1))
 
print(len(loop) // 2)

2023 Day 09 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
from itertools import pairwise
 
def seq(ints):
    if all(ints == 0 for ints in ints):
        return 0
    diffs = [b - a for a, b in pairwise(ints)]
    return ints[-1] + seq(diffs)
 
data = open('input').read().splitlines()
ans = 0
for d in data:
    ans += seq([int(x) for x in d.split()[::-1]])
print(ans)

2023 Day 09 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
from itertools import pairwise
 
def seq(ints):
    if all(ints == 0 for ints in ints):
        return 0
    diffs = [b - a for a, b in pairwise(ints)]
    return ints[-1] + seq(diffs)
 
data = open('input').read().splitlines()
ans = 0
for d in data:
    ans += seq([int(x) for x in d.split()])
print(ans)

2023 Day 08 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import math
from itertools import cycle
 
instr, data = open('input').read().split("\n\n")
data = [x.split(" = ") for x in data.splitlines()]
data = {a: b[1:-1].split(", ") for a, b in data}
 
ans = []
for curr in data:
    if not curr.endswith("A"):
        continue
    visited = set()
    for count, (idx, d) in enumerate(cycle(enumerate(instr)), start=1):
        prev, curr = curr, data[curr][d == "R"]
        visited.add((curr, idx))
        if prev.endswith("Z") and (curr, idx) in visited:
            ans.append(count - 1)
            break
print(math.lcm(*ans))

The least common multiple is the smallest number divisable by all numbers. For example, the lcm of 15 and 25 is 75.

2023 Day 08 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
from itertools import cycle
 
instr, data = open('input').read().split("\n\n")
data = [x.split(" = ") for x in data.splitlines()]
data = {a: b[1:-1].split(", ") for a, b in data}
 
curr = "AAA"
for count, d in enumerate(cycle(instr), start=1):
    curr = data[curr][d == "R"]
    if curr == "ZZZ":
        print(count)
        break

2023 Day 07 Part 02

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
from collections import Counter
from itertools import product
 
 
def get_score(x):
    match [count for _, count in Counter(x).most_common()]:
        case 5, *_:
            return 1
        case 4, *_:
            return 2
        case 3, 2, *_:
            return 3
        case 3, *_:
            return 4
        case 2, 2, *_:
            return 5
        case 2, *_:
            return 6
        case _:
            return 7
 
order = "AKQT98765432J"
vals = []
data = open('input').read().splitlines()
for line in data:
    cards, pts = line.split()
    score = get_score(cards)
    # A joker can represent any of these cards: AKQT98765432
    # For each substitution we calculate the score.
    # We keep the highest score (= lowest value).
    # Note there can be multiple jokers in a hand.
    # Example: 3 jokers -> AAA, AAK, AAQ, etc.
    for subs in product(order[:-1], repeat=cards.count("J")):
        score = min(score, get_score(cards.replace("J", "") + "".join(subs)))
    vals.append((score, [order.index(x) for x in cards], int(pts)))
vals.sort(reverse=True)
print(sum((i + 1) * v[-1] for i, v in enumerate(vals)))

2023 Day 07 Part 01

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
from collections import Counter
 
 
def get_score(x):
    match [count for _, count in Counter(x).most_common()]:
        case 5, *_:     # Five of a kind
            return 1
        case 4, *_:     # Four of a kind
            return 2
        case 3, 2, *_:  # Full House
            return 3
        case 3, *_:     # Three of a kind
            return 4
        case 2, 2, *_:  # Two pairs
            return 5
        case 2, *_:     # Two of a kind
            return 6
        case _:         # Unique
            return 7
 
order = "AKQJT98765432"
vals = []
data = open('input').read().splitlines()
for line in data:
    cards, pts = line.split()
    vals.append((get_score(cards), [order.index(x) for x in cards], int(pts)))
 
vals.sort(reverse=True)
print(sum((i + 1) * v[-1] for i, v in enumerate(vals)))

2023 Day 06 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data = open('input').read().splitlines()
data = [x.split() for x in data]
races = []
for i in range(1,len(data[0])):
    races.append([int(data[0][i]),int(data[1][i])])
 
wins = 0
ans = 1
for r in races:
    for btn in range (0,r[0]):
        dist = (r[0] - btn) * btn
        if dist > r[1]:
            wins += 1
    ans *= wins
    wins = 0
print(ans)

2023 Day 05 Part 02

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
def pairs(l):
    it = iter(l)
    return zip(it, it)
 
seeds, *mappings = open('input').read().split('\n\n')
seeds = seeds.split(": ")[1]
seeds = [int(x) for x in seeds.split()]
seeds = [range(a, a + b) for a, b in pairs(seeds)]
 
for m in mappings:
    _, *ranges = m.splitlines()
    ranges = [[int(x) for x in r.split()] for r in ranges]
    ranges = [(range(a, a + c), range(b, b + c)) for a, b, c in ranges]
 
    new_seeds = []
    for r in seeds:
        for tr, fr in ranges:
            offset = tr.start - fr.start
            if r.stop <= fr.start or fr.stop <= r.start:
                continue
            ir = range(max(r.start, fr.start), min(r.stop, fr.stop))
            lr = range(r.start, ir.start)
            rr = range(ir.stop, r.stop)
            if lr:
                seeds.append(lr)
            if rr:
                seeds.append(rr)
            new_seeds.append(range(ir.start + offset, ir.stop + offset))
            break
        else:
            new_seeds.append(r)
    seeds = new_seeds
 
print(min(x.start for x in seeds))

2023 Day 05 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
seeds, *mappings = open('input').read().split('\n\n')
seeds = seeds.split(": ")[1]
seeds = [int(x) for x in seeds.split()]
 
for m in mappings:
    _, *ranges = m.splitlines()
    ranges = [[int(x) for x in r.split()] for r in ranges]
    ranges = [(range(a, a + c), range(b, b + c)) for a, b, c in ranges]
 
    def translate(x):
        for a, b in ranges:
            if x in b:
                return a.start + x - b.start
        return x
 
    seeds = [translate(x) for x in seeds]
 
print(min(seeds))

2023 Day 04 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data = open("input").read().splitlines()
data = [x.split('|') for x in data]
 
win = [x[0][10:] for x in data]
win = [x.strip().split() for x in win]
 
elf = [x[1] for x in data]
elf = [x.strip().split() for x in elf]
 
dups = []
for _ in elf:
    dups.append(1)
 
for i,card in enumerate(elf):
    hitcount=0
    for num in card:
        if num in win[i]:
            hitcount+=1
            dups[i+hitcount]+=dups[i]
print(sum(dups))

2023 Day 04 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
data = open("input").read().splitlines()
data = [x.split('|') for x in data]
 
win = [x[0][10:] for x in data]
win = [x.strip().split() for x in win]
 
elf = [x[1] for x in data]
elf = [x.strip().split() for x in elf]
 
points = 0
ans = 0
firstwin = True
for i,card in enumerate(elf):
    for num in card:
        if num in win[i]:
            if firstwin:
                points += 1
                firstwin=False
            else:
                points *= 2
    ans += points
    points = 0
    firstwin = True
print(ans)

2023 Day 03 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import re
from collections import defaultdict
 
lines = open("input").read().splitlines()
adj = defaultdict(list)
ans = 0
for i, line in enumerate(lines):
    for m in re.finditer(r"\d+", line):
        idxs = [(i, m.start() - 1), (i, m.end())]  # left, right
        idxs += [(i - 1, j) for j in range(m.start() - 1, m.end() + 1)]  # row above
        idxs += [(i + 1, j) for j in range(m.start() - 1, m.end() + 1)]  # row below
        for a, b in idxs:
            if 0 <= a < len(lines) and 0 <= b < len(lines[a]) and lines[a][b] == "*":  # asterisk nearby within bounds
                adj[a, b].append(m.group())
for v in adj.values():
    if len(v) == 2:  # two numbers near an asterisk
        ans += int(v[0]) * int(v[1])
print (ans)

2023 Day 03 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import re
 
lines = open("input").read().splitlines()    
ans = 0
for i, line in enumerate(lines):
    for m in re.finditer(r"\d+", line):
        # idx contains neighbours
        idxs = [(i, m.start() - 1), (i, m.end())]  # left, right
        idxs += [(i - 1, j) for j in range(m.start() - 1, m.end() + 1)]  # row above
        idxs += [(i + 1, j) for j in range(m.start() - 1, m.end() + 1)]  # row below
        count = sum(0 <= a < len(lines) and 0 <= b < len(lines[a]) and lines[a][b] != "." for a, b in idxs)  # special char nearby within bounds
        if count > 0:
            ans += int(m.group())
print(ans)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import re
 
lines = open("input").read().splitlines()    
ans = 0
for i, line in enumerate(lines):
    for m in re.finditer(r"\d+", line):
        # idx contains neighbours
        idxs = [(i, m.start() - 1), (i, m.end())]  # left, right
        idxs += [(i - 1, j) for j in range(m.start() - 1, m.end() + 1)]  # row above
        idxs += [(i + 1, j) for j in range(m.start() - 1, m.end() + 1)]  # row below
        count = 0
        for a, b in idxs:
            if 0 <= a < len(lines) and 0 <= b < len(lines[a]) and lines[a][b] != ".":  # special char nearby within bounds
                count+=1
        if count > 0:
            ans += int(m.group())
print(ans)
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
def neighbours(grid, r, c):
    vals = sum((row[c -(c>0): c+2] for row in grid[r -(r>0):r+2]), [])
    vals.remove(grid[r][c]) # remove itself.
    return vals 
 
data = open("input").read().splitlines()
grid = list ([[x for x in line] for line in data])
 
ans=0
numstr=""
adj=[]
symbol=False
for x in range(0,len(grid)):
    for y in range(0,len(grid[0])):
        if grid[x][y].isnumeric():
            numstr += grid[x][y]
            # test if symbol nearby
            adj=neighbours(grid,x,y)
            for a in adj:
                if a in ("!","@","#","$","%","^","&","*","(",")","-","+","=","/"):
                    symbol=True
        elif numstr is not "":
            if symbol==True:
                ans+=int(numstr)
                symbol=False
            numstr=""
print(ans)

2023 Day 02 Part 02

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
data = open("input").read().splitlines()
data = [x.replace(',','') for x in data]
data = [x.split(';') for x in data]
ans=0
for d in data:
    rmax,gmax,bmax=0,0,0
    for e in d:
        r,g,b=0,0,0
        prev=0
        ff = e.split(' ')
        for f in ff:
            match f:
                case 'red':
                    r+=int(prev)
                case 'green':
                    g+=int(prev)
                case 'blue':
                    b+=int(prev)
            prev = f
        if r>rmax:
            rmax = r
        if g>gmax:
            gmax = g
        if b>bmax:
            bmax = b
    ans+=rmax*gmax*bmax
print(ans)

2023 Day 02 Part 01

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
data = open("input").read().splitlines()
data = [x.replace(',','') for x in data]
data = [x.split(';') for x in data]
ans=0
round=1
for d in data:
    for e in d:
        r,g,b=0,0,0
        prev=0
        possible=True
        ff = e.split(' ')
        for f in ff:
            match f:
                case 'red':
                    r+=int(prev)
                case 'green':
                    g+=int(prev)
                case 'blue':
                    b+=int(prev)
            prev = f
        if r>12 or g>13 or b>14:
            possible=False
            break
    if possible == True:
        ans+=round
    round+=1
print(ans)

2023 Day 01 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
lines = open("input").read().splitlines()
p = 0
for line in lines:
    digits = []
    for i,c in enumerate(line):
        if c.isdigit():
            digits.append(c)
        for d,val in enumerate(['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']):
            if line[i:].startswith(val):
                digits.append(str(d+1))
    p += int(digits[0]+digits[-1])
print(p)

2023 Day 01 Part 01

1
2
3
4
5
6
lines = open("input").read().splitlines()
p = 0
for line in lines:    
    digits = [c for c in line if c.isdigit()]
    p += int(digits[0] + digits[-1])
print(p)
1
2
3
4
5
6
7
8
9
lines = open("input").read().splitlines()
p = 0
for line in lines:
    digits = []
    for c in line:
        if c.isdigit():
            digits.append(c)
    p += int(digits[0]+digits[-1])
print(p)