Skip to content

Commit 8b3d799

Browse files
committed
Year 2024 Day 9
1 parent d886476 commit 8b3d799

File tree

7 files changed

+150
-0
lines changed

7 files changed

+150
-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: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 which makes it very fast.
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 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 allocate any memory.
32+
pub fn part1(disk: &[usize]) -> usize {
33+
// Start at the first free block and the last file.
34+
let mut free = 0;
35+
let mut file = disk.len() + disk.len() % 2;
36+
37+
let mut available = 0;
38+
let mut needed = 0;
39+
40+
let mut block = 0;
41+
let mut checksum = 0;
42+
43+
while free < file {
44+
// Take as much space as possible from the current free block range.
45+
let size = needed.min(available);
46+
(checksum, block) = update(checksum, block, file, size);
47+
available -= size;
48+
needed -= size;
49+
50+
// One or both of "available" and "free" could be zero.
51+
if needed == 0 {
52+
file -= 2;
53+
needed = disk[file];
54+
}
55+
56+
// When moving to the next free block, add the checksum for the file we're skipping over.
57+
if available == 0 {
58+
let size = disk[free];
59+
(checksum, block) = update(checksum, block, free, size);
60+
available = disk[free + 1];
61+
free += 2;
62+
}
63+
}
64+
65+
// Account for any remaining file blocks left over.
66+
(checksum, _) = update(checksum, block, file, needed);
67+
checksum
68+
}
69+
70+
pub fn part2(disk: &[usize]) -> usize {
71+
let mut block = 0;
72+
let mut checksum = 0;
73+
let mut free: [_; 10] = from_fn(|_| BinaryHeap::with_capacity(1_000));
74+
75+
// Build a min-heap (leftmost free block first) where the size of each block is
76+
// implicit in the index of the array.
77+
for (index, &size) in disk.iter().enumerate() {
78+
if index % 2 == 1 && size > 0 {
79+
free[size].push(Reverse(block));
80+
}
81+
82+
block += size;
83+
}
84+
85+
for (index, &size) in disk.iter().enumerate().rev() {
86+
block -= size;
87+
88+
// Count any previous free blocks to decrement block offset correctly.
89+
if index % 2 == 1 {
90+
continue;
91+
}
92+
93+
// Find the leftmost free block that can fit the file (if any).
94+
let mut next_block = block;
95+
let mut next_index = usize::MAX;
96+
97+
#[allow(clippy::needless_range_loop)]
98+
for i in size..10 {
99+
if let Some(&Reverse(first)) = free[i].peek() {
100+
if first < next_block {
101+
next_block = first;
102+
next_index = i;
103+
}
104+
}
105+
}
106+
107+
// Update the checksum with the file's location (possibly unchanged).
108+
let id = index / 2;
109+
let extra = next_block * size + EXTRA[size];
110+
checksum += id * extra;
111+
112+
// If we used a free block, remove then add back any leftover space.
113+
if next_index != usize::MAX {
114+
free[next_index].pop();
115+
if size < next_index {
116+
free[next_index - size].push(Reverse(next_block + size));
117+
}
118+
}
119+
}
120+
121+
checksum
122+
}
123+
124+
/// Convenience function to update checksum based on file location and size.
125+
#[inline]
126+
fn update(checksum: usize, block: usize, index: usize, size: usize) -> (usize, usize) {
127+
let id = index / 2;
128+
let extra = block * size + EXTRA[size];
129+
(checksum + id * extra, block + size)
130+
}

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)