AoC 2025 Advent of Code (Python)

By telleropnul, November 28, 2025

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: adventofcode2025inputs.zip

2025 Day 12 Part 02

1
 

2025 Day 12 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
groups = [group.split("\n") for group in open("input").read().split("\n\n")]
 
gift_sizes = {}
 
for g in groups[:-1]:
    id = int(g[0][:-1])
    size = sum([r.count("#") for r in g])
    gift_sizes[id] = size
 
p1 = 0
 
for r in groups[-1]:
    width, length = r.split(":")[0].split("x")
    space = int(width) * int(length)
    required = r.split(" ")[1:]
    space_req = 0
    for i, v in enumerate(required):
        space_req += int(v) * gift_sizes[i]
    p1 += space >= space_req
 
print(p1)

2025 Day 11 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
from collections import defaultdict
 
lines = open("input").read().splitlines()
 
outputs = defaultdict(list)
for line in lines:
    a, b = line.split(": ")
    outputs[a] = b.split(" ")
 
visited = {}
 
def dfs(current, target):
    key = (current, target)
    if key in visited:
        return visited[key]
    if current == target:
        return 1
    out = 0
    for n in outputs[current]:
        out += dfs(n, target)
    visited[key] = out
    return out
 
p1 = dfs("you", "out")
print(p1)
 
if dfs("dac", "out") > 0:
    p2 = dfs("svr", "fft") * dfs("fft", "dac") * dfs("dac", "out") 
else:
    p2 = dfs("svr", "dac") * dfs("dac", "fft") * dfs("fft", "out") 
 
print(p2)

2025 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 defaultdict
 
lines = open("input").read().splitlines()
 
outputs = defaultdict(list)
for line in lines:
    a, b = line.split(":")
    outputs[a].append(b.split(" "))
 
def dfs(node):
    count = 0
    for b in outputs[node]:
        for a in b:
            count += dfs(a)
            print(a)
            if a == "out":
                return 1
    return count
 
print(dfs("you"))

2025 Day 10 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
# python -m pip install z3-solver
from z3 import *
 
lines = open("input").read().splitlines()
 
 
def f(target, buttons):
    open = {frozenset()}
    completed = set()
    clicks = 0
    while True:
        next_open = set()
        completed |= open
        for current in open:
            if current == target:
                return clicks
            for b in buttons:
                next = set(current)
                for i in b:
                    if i in next:
                        next.remove(i)
                    else:
                        next.add(i)
                if next not in completed:
                    next_open.add(frozenset(next))
        open = next_open
        clicks += 1
 
 
def solve(target, buttons):
    my_optimizer = z3.Optimize()
 
    clicks = [Int(i) for i in range(len(buttons))]
 
    for i, target_i in enumerate(target):
        count = 0
        for j, b in enumerate(buttons):
            if i in b:
                count += clicks[j]
        my_optimizer.add(count == target_i)
 
    my_optimizer.add(And([c >= 0 for c in clicks]))
 
    my_optimizer.minimize(sum(clicks))
    my_optimizer.check()
    model = my_optimizer.model()
    return sum([model.eval(c).as_long() for c in clicks])
 
 
p1 = 0
p2 = 0
 
for line in lines:
    target_raw = line.split(" ")[0][1:-1]
    target = set()
    for i, v in enumerate(target_raw):
        if v == "#":
            target.add(i)
 
    buttons = []
    for buttons_raw in line.split(" ")[1:-1]:
        buttons.append([int(b) for b in buttons_raw[1:-1].split(",")])
 
    joltage = [int(j) for j in line.split("{")[1][:-1].split(",")]
 
    p1 += f(target, buttons)
    p2 += solve(joltage, buttons)
 
print("Part 1: " + str(p1))
print("Part 2: " + str(p2))

2025 Day 09 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
lines = open("input").read().splitlines()
 
def is_valid(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    top = min(y1, y2)
    right = max(x1, x2)
    lower = max(y1, y2)
    left = min(x1, x2)
 
    for w in walls:
        c1, c2 = w
        wall_top = min(c1[1], c2[1])
        wall_lower = max(c1[1], c2[1])
        wall_left = min(c1[0], c2[0])
        wall_right = max(c1[0], c2[0])
        if left < wall_right and \
           wall_left < right and \
           top < wall_lower and \
           wall_top < lower:
            return False
 
    return True
 
 
corners = []
 
for l in lines:
    a, b = l.split(",")
    a = int(a)
    b = int(b)
    corners.append((a, b))
 
walls = list(zip(corners, corners[1:]))
walls.append((corners[-1], corners[0]))
 
p1 = 0
p2 = 0
 
for c1 in corners:
    for c2 in corners:
        if c1 > c2:
            x1, y1 = c1
            x2, y2 = c2
            size = (abs(x1 - x2) + 1) * (abs(y1 - y2) + 1)
            p1 = max(size, p1)
            if size > p2:
                if is_valid(c1, c2):
                    p2 = size
 
print(p1)
print(p2)

2025 Day 08 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
lines = open("input").read().splitlines()
boxes = [list(map(int, line.split(","))) for line in lines]
 
pairs = {}
for i, box1 in enumerate(boxes):
    for j, box2 in enumerate(boxes[i + 1 :], i + 1):
        pairs[(i, j)] = sum((a - b) ** 2 for a, b in zip(box1, box2))
 
pairs = sorted(pairs.items(), key=lambda x: x[1])
 
connected_sets: list[set] = []
connected = set()
 
for (i, j), d in pairs:
    connected.add(i)
    connected.add(j)
 
    _id_i = -1
    _id_j = -1
 
    for idx, s in enumerate(connected_sets):
        if i in s:
            _id_i = idx
        if j in s:
            _id_j = idx
 
    match (_id_i > -1, _id_j > -1):
        case (False, False):
            connected_sets.append(set([i, j]))
        case (True, False):
            connected_sets[_id_i].add(j)
        case (False, True):
            connected_sets[_id_j].add(i)
        case (True, True) if _id_i != _id_j:
            connected_sets[_id_i] |= connected_sets[_id_j]
            del connected_sets[_id_j]
 
    if len(connected) == len(boxes) and len(connected_sets) == 1:
        print (boxes[i][0] * boxes[j][0])
        exit()

2025 Day 08 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
lines = open("input").read().splitlines()
boxes = [list(map(int, line.split(","))) for line in lines]
 
pairs = {}
for i, box1 in enumerate(boxes):
    for j, box2 in enumerate(boxes[i + 1 :], i + 1):
        pairs[(i, j)] = sum((a - b) ** 2 for a, b in zip(box1, box2))
 
pairs = sorted(pairs.items(), key=lambda x: x[1])
 
# nums = 10 if it's test data
nums = 10 if len(boxes) == 20 else 1000
pairs = pairs[:nums]
 
connected_sets: list[set] = []
 
for (i, j), d in pairs:
    _id_i = -1
    _id_j = -1
 
    for idx, s in enumerate(connected_sets):
        if i in s:
            _id_i = idx
        if j in s:
            _id_j = idx
 
    match (_id_i > -1, _id_j > -1):
        case (False, False):
            connected_sets.append(set([i, j]))
        case (True, False):
            connected_sets[_id_i].add(j)
        case (False, True):
            connected_sets[_id_j].add(i)
        case (True, True) if _id_i != _id_j:
            connected_sets[_id_i] |= connected_sets[_id_j]
            del connected_sets[_id_j]
 
_lens = sorted([len(s) for s in connected_sets], reverse=True)
print (_lens[0] * _lens[1] * _lens[2])

2025 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
grid = open("input").read().splitlines()
h, w = len(grid), len(grid[0])
timelines = {}
 
def f(p):
    if p in timelines:
        return timelines[p]
    # We have not been here before.
    x, y = p
    if y >= h:
        return 1  # We have reached the end of 1 timeline.
    if p in splitters:
        timelines[p] = f((x - 1, y + 1)) + f((x + 1, y + 1))
    else:
        timelines[p] = f((x, y + 1))
    return timelines[p]
 
start = None
splitters = set()
for y in range(0, h):
    for x in range(0, w):
        p = (x , y)
        c = grid[y][x]
        if c == 'S':
            start = p
        elif c == '^':
            splitters.add(p)
 
print (f(start))

2025 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
grid = open("input").read().splitlines()
h, w = len(grid), len(grid[0])
 
start = None
splitters = set()
for y in range(0, h):
    for x in range(0, w):
        p = (x , y)
        c = grid[y][x]
        if c == 'S':
            start = p
        elif c == '^':
            splitters.add(p)
 
open = {start}
splitted = set()
while open:
    p = open.pop()
    x, y = p
    if y < h:
        if p in splitters:
            splitted.add(p)
            open.add((x + 1, y + 1))
            open.add((x - 1, y + 1))
        else:
            open.add((x, y + 1))
 
print(len(splitted))

2025 Day 06 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
from math import prod
 
data = open("input").read().splitlines()
operands = data[-1].split()
tot = 0
op = 0
nums = []
for j in range(0, len(data[0])):
    pos_num = ""
    # Get the column number.
    for i in range(0, len(data)-1):
        if data[i][j] != " ":
            pos_num += data[i][j]
 
    # If the column number is all whitespace,
    # then the numbers end, and you operate them.
    # Same if it is the last column.
    if pos_num == "" or j == len(data[0])-1:
        if j == len(data[0])-1:
            nums.append(int(pos_num))
        operand = operands[op]
        if operand == '*':
            tot += prod(nums)
        else:
            tot += sum(nums)
        nums = []
        op += 1
 
    # If the column contains some number,
    # delete the whitespace and add the number.
    else:
        nums.append(int(pos_num))
print(tot)

2025 Day 06 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from math import prod
 
lines = open("input").read().splitlines()
lines = [(line.split(" ")) for line in lines]
 
tot = 0
for x in range (0, len(lines[0])):
    args = []
    for y in range (0, len(lines)-1):
        args.append(int(lines[y][x]))
    if lines[len(lines)-1][x] == '*':
        tot += prod(args)
    else:
        tot += sum(args)
print(tot)

2025 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
lines, _ = open("input").read().split("\n\n")
 
ranges = []
for line in lines.splitlines():
    start, end = map(int, line.split('-'))
    ranges.append((start, end))
 
# Sort ranges by start value
ranges.sort()
 
# Merge overlapping ranges
merged = []
for start, end in ranges:
    if not merged:
        merged.append((start, end))
    else:
        last_start, last_end = merged[-1]
        # If current range overlaps or is adjacent to the last merged range
        if start <= last_end + 1:
            # Merge: extend the last range if needed
            merged[-1] = (last_start, max(last_end, end))
        else:
            # No overlap: add as new range
            merged.append((start, end))
 
# Calculate total number of IDs in all merged ranges
total = 0
for start, end in merged:
    total += (end - start + 1)
 
print (total)

2025 Day 05 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
IDranges, IDs = open("input").read().split("\n\n")
 
IDranges = IDranges.splitlines()
IDs = IDs.splitlines()
 
fresh = 0
for ID in IDs:
    for IDrange in IDranges:
        start,end = IDrange.split("-")
        if int(start) <= int(ID) <= int(end):
            #print(ID)
            fresh += 1
            break
print (fresh)

2025 Day 04 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
grid  = open("input").read().splitlines()
 
grid1 = grid.copy()
 
h, w = len(grid), len(grid[0])
 
moves = [(-1,-1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
 
def change_letter(string, letter, index):  # string is bad variable name.
    return string[:index] + letter + string[index+1:]
 
def loc(y, x):
    if y < 0 or x < 0:
        return None
    try:
        return grid[y][x]
    except Exception:
        return None
 
def bfs(start):
    cur = set([start])
    rolls = 0
    for y, x in list(cur):
        if loc(y, x) == '@':
            for ny, nx in list((y+dy, x+dx) for dy, dx in moves):
                if loc(ny, nx) == '@':
                    rolls +=1
            if rolls < 4:
                # grid1[y][x] = '.'  No can't do.
                line = change_letter(grid1[y], '.', x)
                grid1[y] = line
                return 1
            else:
                return 0
        else:
            return 0
 
def run_grid():
    tot = 0
    for y in range(h):
        for x in range(w):
            tot += bfs((y, x))
    return (tot)
 
tots = 0
tots_previous = -1
while True:
    tots += run_grid()
    if tots == tots_previous:
        break
    grid = grid1.copy()
    tots_previous = tots
print(tots)

2025 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
25
26
27
28
29
30
31
32
33
34
35
grid = open("input").read().splitlines()
 
h, w = len(grid), len(grid[0])
 
moves = [(-1,-1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
 
def loc(y, x):
    if y < 0 or x < 0:
        return None
    try:
        return grid[y][x]
    except Exception:
        return None
 
def bfs(start):
    cur = set([start])
    rolls = 0
    for y, x in list(cur):
        if loc(y, x) == '@':
            for ny, nx in list((y+dy, x+dx) for dy, dx in moves):
                if loc(ny, nx) == '@':
                    rolls +=1
            if rolls < 4:
                return 1
            else:
                return 0
        else:
            return 0
 
 
tot = 0
for y in range(h):
    for x in range(w):
      tot += bfs((y, x))
print(tot)

2025 Day 03 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
def find_max_joltage(n: int) -> int:
    res = 0
    digits = [int(char) for char in str(n)]
    left = 0
    right = len(digits) - 12
    while right < len(digits):
        max_tuple = left, digits[left]
        for i in range(left + 1, right + 1):
            if digits[i] > digits[max_tuple[0]]:
                max_tuple = i, digits[i]
        res = res * 10 + max_tuple[1]
        right += 1
        left = max_tuple[0] + 1
    return res
 
 
lines = open("input").read().splitlines()
sumjolts = 0
for line in lines:
    maxjolt = find_max_joltage(line)
    print(maxjolt)
    sumjolts += maxjolt
print (sumjolts)

2025 Day 03 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
lines = open("input").read().splitlines()
maxjolt = 0
sumjolts = 0
for line in lines:
    for i, x in enumerate(line):
        for y in range (i+1, len(line)):
            jolt = int(x+line[y])
            #jolt = int(''.join([x,line[y]]))
            if jolt > maxjolt:
                maxjolt = jolt
    sumjolts += maxjolt
    maxjolt = 0
print (sumjolts)

2025 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
28
29
30
31
32
def check_string_repeats(s: str) -> bool:
    """
    Purpose:
        Checks if the string consists of multiple repeats.
 
    Arguments:
        s: The input string.
 
    Returns:
        True if the string is a repeated sequence, False otherwise.
    """
        # Initialize result as False
    res = False
    # Iterate through possible lengths of substrings
    for i in range(1, len(s) // 2 + 1):
        # Check if current length divides the string evenly
        if len(s) % i == 0:
            # Check if the substring repeats to form the string
            if s[:i] * (len(s) // i) == s:
                res = True
                break
    return(res)
 
 
suminvalids = 0
lines = open("input").read().split(",")
for line in lines:
    firstID, lastID = line.split("-")
    for s in range (int(firstID), int(lastID)+1):
        if check_string_repeats(str(s)):
            suminvalids += int(s)
print(suminvalids)

2025 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
28
29
30
31
32
33
34
35
def check_string_halves_equality(s: str) -> bool:
    """
    Purpose:
        Checks if the first half of a string is equal to its second half.
 
    Arguments:
        s: The input string.
 
    Returns:
        True if the first half is equal to the second half, False otherwise.
        Returns False if the string has an odd length.
    """
 
    n = len(s)
 
    # If string length is odd, 
    # it cannot be perfectly divided into two equal halves.
    if n % 2 != 0:
        return False
 
    midpoint = n // 2
    first_half = s[:midpoint]
    second_half = s[midpoint:]
 
    return first_half == second_half
 
 
suminvalids = 0
lines = open("input").read().split(",")
for line in lines:
    firstID, lastID = line.split("-")
    for s in range(int(firstID), int(lastID)+1):
        if check_string_halves_equality(str(s)):
            suminvalids += int(s)
print(suminvalids)

2025 Day 01 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
lines = open("input").read().splitlines()
dial = 50
lastdial = 50
countzero = 0
for line in lines:
    countzero += int(line[1:]) // 100
    if line[0] == "L":
        dial -= int(line[1:]) % 100
    elif line[0] == "R":
        dial += int(line[1:]) % 100
 
    if dial < 0:
        dial += 100
        if lastdial != 0:
            countzero += 1
    if dial > 99:
        dial -= 100
        if dial != 0:
            countzero += 1
 
    if dial == 0:
        countzero += 1
 
    print (dial, countzero)
    lastdial = dial
 
print (countzero)

2025 Day 01 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lines = open("input").read().splitlines()
dial = 50
countzero = 0
for line in lines:
    if line[0] == "L":
        dial -= int(line[1:]) % 100
    elif line[0] == "R":
        dial += int(line[1:]) % 100
    if dial < 0:
        dial += 100
    if dial > 99:
        dial -= 100
    if dial == 0:
        countzero += 1
print (countzero)