-
Notifications
You must be signed in to change notification settings - Fork 0
/
day9.py
141 lines (104 loc) · 3.17 KB
/
day9.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# Advent of Code 2024, Day 9
# (c) blu3r4y
from aocd.models import Puzzle
from funcy import print_calls, print_durations
@print_calls
@print_durations(unit="ms")
def part1(data):
blocks = make_blocks(data)
move_blocks(blocks)
return checksum_blocks(blocks)
def make_blocks(data):
blocks = []
free, fid = False, 0
for fsize in data:
if free:
blocks.extend([None] * fsize)
else:
blocks.extend([fid] * fsize)
fid += 1
free = not free
assert len(blocks) == sum(data)
return blocks
def move_blocks(data):
front, back = 0, len(data) - 1
dst, src = None, None
while front < back:
# take first non-empty block from the back
if src is None:
if data[back] is not None:
src = back
else:
back -= 1
# take first empty block from the front
if dst is None:
if data[front] is None:
dst = front
else:
front += 1
# swap blocks if both are found
if dst is not None and src is not None:
data[dst], data[src] = data[src], data[dst]
dst, src = None, None
back -= 1
front += 1
def checksum_blocks(data):
result = 0
for bid, bsize in enumerate(data):
if bsize is not None:
result += bid * bsize
return result
@print_calls
@print_durations(unit="ms")
def part2(data):
blocks = make_files(data)
move_files(blocks)
return checksum_files(blocks)
def make_files(data):
blocks = []
free, fid = False, 0
for fsize in data:
if free:
blocks.append((None, fsize))
else:
blocks.append((fid, fsize))
fid += 1
free = not free
return blocks
def move_files(blocks):
# defragment in order of decreasing file id
fid = max(fid for fid, _ in blocks if fid is not None)
while fid > 0:
# index and size of next file block
fi, fsize = next((i, size) for i, (k, size) in enumerate(blocks) if k == fid)
# check for a large enough spot from left to right
for ei, (eid, esize) in enumerate(blocks):
if eid is not None:
continue # not a free block
if ei > fi:
break # don't move blocks to the right
if esize >= fsize:
blocks[fi] = (None, fsize) # old
blocks[ei] = (fid, fsize) # new
if esize - fsize > 0:
blocks.insert(ei + 1, (None, esize - fsize)) # leftover
break
fid -= 1
def checksum_files(blocks):
result = 0
pos = 0
for fid, fsize in blocks:
if fid is not None:
result += sum(fid * i for i in range(pos, pos + fsize))
pos += fsize
return result
def load(data):
return list(map(int, data.strip()))
if __name__ == "__main__":
puzzle = Puzzle(year=2024, day=9)
ans1 = part1(load(puzzle.input_data))
assert ans1 == 6337367222422
puzzle.answer_a = ans1
ans2 = part2(load(puzzle.input_data))
assert ans2 == 6361380647183
puzzle.answer_b = ans2