Skip to content

Commit e15b1ac

Browse files
committed
Year 2024 Day 9
1 parent d886476 commit e15b1ac

File tree

7 files changed

+152
-0
lines changed

7 files changed

+152
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
8080
| 6 | [Guard Gallivant](https://adventofcode.com/2024/day/6) | [Source](src/year2024/day06.rs) | 386 |
8181
| 7 | [Bridge Repair](https://adventofcode.com/2024/day/7) | [Source](src/year2024/day07.rs) | 136 |
8282
| 8 | [Resonant Collinearity](https://adventofcode.com/2024/day/8) | [Source](src/year2024/day08.rs) | 8 |
83+
| 9 | [Disk Fragmenter](https://adventofcode.com/2024/day/9) | [Source](src/year2024/day09.rs) | 187 |
8384

8485
## 2023
8586

benches/benchmark.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,5 @@ mod year2024 {
300300
benchmark!(year2024, day06);
301301
benchmark!(year2024, day07);
302302
benchmark!(year2024, day08);
303+
benchmark!(year2024, day09);
303304
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,5 @@ pub mod year2024 {
299299
pub mod day06;
300300
pub mod day07;
301301
pub mod day08;
302+
pub mod day09;
302303
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,5 +369,6 @@ fn year2024() -> Vec<Solution> {
369369
solution!(year2024, day06),
370370
solution!(year2024, day07),
371371
solution!(year2024, day08),
372+
solution!(year2024, day09),
372373
]
373374
}

src/year2024/day09.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//! # Disk Fragmenter
2+
//!
3+
//! ## Part One
4+
//!
5+
//! Computes the checksum by simultaneously scanning forward for free blocks and
6+
//! backwards for files. No memory is allocated.
7+
//!
8+
//! ## Part Two
9+
//!
10+
//! We build 10 [min heaps](https://en.wikipedia.org/wiki/Heap_(data_structure)) in an array to
11+
//! store the free space offsets. The index of the array implicitly stores the size of the
12+
//! free block.
13+
//!
14+
//! When moving a file to a free block, the corresponding heap is popped and then any leftover
15+
//! space is pushed back to the heap at a smaller index. The heap at index zero is not used
16+
//! but makes the indexing easier.
17+
use std::array::from_fn;
18+
use std::cmp::Reverse;
19+
use std::collections::BinaryHeap;
20+
21+
/// [Triangular numbers](https://en.wikipedia.org/wiki/Triangular_number) offset by two.
22+
/// Files can be a max size of 9 so we only need the first 10 values, including zero only to make
23+
/// indexing easier.
24+
const EXTRA: [usize; 10] = [0, 0, 1, 3, 6, 10, 15, 21, 28, 36];
25+
26+
/// Remove any trailing newlines and convert to `usize`.
27+
pub fn parse(input: &str) -> Vec<usize> {
28+
input.trim().bytes().map(|b| (b - b'0') as usize).collect()
29+
}
30+
31+
/// Block by block checksum comparison that doesn't modify or allocate any memory.
32+
pub fn part1(disk: &[usize]) -> usize {
33+
let size = disk.len() - 1;
34+
35+
// Start at the first free block and the last file.
36+
let mut free = 0;
37+
let mut file = size - size % 2 + 2;
38+
39+
let mut available = 0;
40+
let mut needed = 0;
41+
42+
let mut block = 0;
43+
let mut checksum = 0;
44+
45+
while free < file {
46+
// Take as much space as possible from the current free block range.
47+
let size = needed.min(available);
48+
(checksum, block) = update(checksum, block, file, size);
49+
available -= size;
50+
needed -= size;
51+
52+
// One or both of "available" and "free" could be zero.
53+
if needed == 0 {
54+
file -= 2;
55+
needed = disk[file];
56+
}
57+
58+
// When moving to the next free block, add the checksum for the file we're skipping over.
59+
if available == 0 {
60+
let size = disk[free];
61+
(checksum, block) = update(checksum, block, free, size);
62+
available = disk[free + 1];
63+
free += 2;
64+
}
65+
}
66+
67+
// Account for any remaining file blocks left over.
68+
(checksum, _) = update(checksum, block, file, needed);
69+
checksum
70+
}
71+
72+
pub fn part2(disk: &[usize]) -> usize {
73+
let mut block = 0;
74+
let mut checksum = 0;
75+
let mut free: [_; 10] = from_fn(|_| BinaryHeap::with_capacity(1_000));
76+
77+
// Build a min-heap (leftmost file block first) where the size of each block is
78+
// implicit in the size of the array.
79+
for (index, &size) in disk.iter().enumerate() {
80+
if index % 2 == 1 && size > 0 {
81+
free[size].push(Reverse(block));
82+
}
83+
84+
block += size;
85+
}
86+
87+
for (index, &size) in disk.iter().enumerate().rev() {
88+
block -= size;
89+
90+
// Count any previous free blocks to decrement block offset correctly.
91+
if index % 2 == 1 {
92+
continue;
93+
}
94+
95+
// Find the leftmost free block that can fit the file (if any).
96+
let mut next_block = block;
97+
let mut next_index = usize::MAX;
98+
99+
#[allow(clippy::needless_range_loop)]
100+
for i in size..10 {
101+
if let Some(&Reverse(first)) = free[i].peek() {
102+
if first < next_block {
103+
next_block = first;
104+
next_index = i;
105+
}
106+
}
107+
}
108+
109+
// Update the checksum with the file's location (possibly unchanged).
110+
let id = index / 2;
111+
let extra = next_block * size + EXTRA[size];
112+
checksum += id * extra;
113+
114+
// If we used a free block, remove then add back any leftover space.
115+
if next_index != usize::MAX {
116+
free[next_index].pop();
117+
if size < next_index {
118+
free[next_index - size].push(Reverse(next_block + size));
119+
}
120+
}
121+
}
122+
123+
checksum
124+
}
125+
126+
/// Convenience function to update checksum based on file location and size.
127+
#[inline]
128+
fn update(checksum: usize, block: usize, index: usize, size: usize) -> (usize, usize) {
129+
let id = index / 2;
130+
let extra = block * size + EXTRA[size];
131+
(checksum + id * extra, block + size)
132+
}

tests/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,5 @@ mod year2024 {
289289
mod day06_test;
290290
mod day07_test;
291291
mod day08_test;
292+
mod day09_test;
292293
}

tests/year2024/day09_test.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use aoc::year2024::day09::*;
2+
3+
const EXAMPLE: &str = "2333133121414131402";
4+
5+
#[test]
6+
fn part1_test() {
7+
let input = parse(EXAMPLE);
8+
assert_eq!(part1(&input), 1928);
9+
}
10+
11+
#[test]
12+
fn part2_test() {
13+
let input = parse(EXAMPLE);
14+
assert_eq!(part2(&input), 2858);
15+
}

0 commit comments

Comments
 (0)