Skip to content

Commit 485d01b

Browse files
committed
2024 day 9
1 parent c9fdc18 commit 485d01b

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

crates/year2024/src/day09.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
use std::collections::VecDeque;
2+
use utils::prelude::*;
3+
4+
/// Implementing disk defragmentation.
5+
#[derive(Clone, Debug)]
6+
pub struct Day09<'a> {
7+
input: &'a str,
8+
total_length: u32,
9+
}
10+
11+
impl<'a> Day09<'a> {
12+
pub fn new(input: &'a str, _: InputType) -> Result<Self, InputError> {
13+
let mut pos = 0;
14+
for b in input.bytes() {
15+
if b.is_ascii_digit() {
16+
pos += (b - b'0') as u32;
17+
} else {
18+
return Err(InputError::new(input, b as char, "expected digit"));
19+
}
20+
}
21+
Ok(Self {
22+
input,
23+
total_length: pos,
24+
})
25+
}
26+
27+
#[must_use]
28+
pub fn part1(&self) -> u64 {
29+
let mut free_iterator = self.free_space_iter();
30+
let mut file_iterator = self.rev_file_iter();
31+
32+
let mut checksum = 0;
33+
let (mut free_pos, mut free_len) = (0, 0);
34+
let (mut file_pos, mut file_len, mut file_id) = (0, 0, 0);
35+
loop {
36+
if free_len == 0 {
37+
let Some(next) = free_iterator.next() else {
38+
break;
39+
};
40+
(free_pos, free_len) = next;
41+
}
42+
if file_len == 0 {
43+
let Some(next) = file_iterator.next() else {
44+
break;
45+
};
46+
(file_pos, file_len, file_id) = next;
47+
}
48+
49+
if free_pos > file_pos {
50+
break;
51+
}
52+
53+
let len = free_len.min(file_len);
54+
checksum += Self::file_checksum(free_pos, len, file_id);
55+
free_pos += len;
56+
free_len -= len;
57+
file_len -= len;
58+
}
59+
60+
// Handle the remaining file_len that wasn't moved into a free space, if any
61+
if file_len > 0 {
62+
checksum += Self::file_checksum(file_pos, file_len, file_id);
63+
}
64+
65+
// Handle remaining complete files, if any
66+
for (file_pos, file_len, file_id) in file_iterator {
67+
checksum += Self::file_checksum(file_pos, file_len, file_id);
68+
}
69+
70+
checksum
71+
}
72+
73+
#[must_use]
74+
pub fn part2(&self) -> u64 {
75+
// Each VecDeque stores a lazily populated sorted list of free space positions that are i+1
76+
// long. Elements are always popped from the start of the deque, and newly parsed free
77+
// spaces are always pushed to the end of the deque.
78+
//
79+
// When a file is moved and partially consumes a free space, the remainder has to be
80+
// inserted into the correct index to maintain the sort order, which may be in the middle of
81+
// the deque. However, this happens relatively infrequently in the input (<400 times),
82+
// and in my testing using a deque is faster than using a BTreeSet or BinaryHeap.
83+
let mut free_positions: [_; 9] =
84+
std::array::from_fn(|_| VecDeque::with_capacity(self.input.len() / 10));
85+
for (pos, len) in self.free_space_iter() {
86+
free_positions[len as usize - 1].push_back(pos);
87+
}
88+
89+
let mut checksum = 0;
90+
let mut file_iterator = self.rev_file_iter();
91+
for (file_pos, file_len, file_id) in &mut file_iterator {
92+
let (mut next_pos, mut free_len) = (file_pos, 0);
93+
94+
// Check for free spaces at least as long as the file before the file's position
95+
for len in file_len..=9 {
96+
if let Some(&pos) = free_positions[len as usize - 1].front() {
97+
if pos < next_pos {
98+
(next_pos, free_len) = (pos, len);
99+
}
100+
}
101+
}
102+
103+
checksum += Self::file_checksum(next_pos, file_len, file_id);
104+
105+
if next_pos < file_pos {
106+
// Remove now used free space, and add remaining space if any
107+
free_positions[free_len as usize - 1].pop_front();
108+
if free_len > file_len {
109+
let (pos, len) = (next_pos + file_len, free_len - file_len);
110+
match free_positions[len as usize - 1].binary_search(&pos) {
111+
Ok(_) => unreachable!(),
112+
Err(i) => free_positions[len as usize - 1].insert(i, pos),
113+
}
114+
}
115+
} else if file_len == 1 {
116+
// If there is no space prior to the current file to move a 1-length file, then
117+
// either there is no free space remaining or it is all after the remaining files
118+
break;
119+
}
120+
}
121+
122+
// Handle remaining files, if any
123+
for (file_pos, file_len, file_id) in file_iterator {
124+
checksum += Self::file_checksum(file_pos, file_len, file_id);
125+
}
126+
127+
checksum
128+
}
129+
130+
fn free_space_iter(&self) -> impl Iterator<Item = (u32, u32)> + use<'_> {
131+
struct FreeSpaceIterator<'a> {
132+
input: &'a [u8],
133+
pos: u32,
134+
}
135+
136+
impl Iterator for FreeSpaceIterator<'_> {
137+
type Item = (u32, u32);
138+
139+
#[inline]
140+
fn next(&mut self) -> Option<Self::Item> {
141+
while self.input.len() >= 2 {
142+
let file_len = (self.input[0] - b'0') as u32;
143+
let free_len = (self.input[1] - b'0') as u32;
144+
let free_pos = self.pos + file_len;
145+
146+
self.input = &self.input[2..];
147+
self.pos += file_len + free_len;
148+
149+
if free_len > 0 {
150+
return Some((free_pos, free_len));
151+
}
152+
}
153+
None
154+
}
155+
}
156+
157+
FreeSpaceIterator {
158+
input: self.input.as_bytes(),
159+
pos: 0,
160+
}
161+
}
162+
163+
fn rev_file_iter(&self) -> impl Iterator<Item = (u32, u32, u32)> + use<'_> {
164+
struct ReverseFileIterator<'a> {
165+
input: &'a [u8],
166+
pos: u32,
167+
id: u32,
168+
}
169+
170+
impl Iterator for ReverseFileIterator<'_> {
171+
type Item = (u32, u32, u32);
172+
173+
#[inline]
174+
fn next(&mut self) -> Option<Self::Item> {
175+
while !self.input.is_empty() {
176+
if self.input.len() % 2 == 0 {
177+
self.pos -= (self.input[self.input.len() - 1] - b'0') as u32;
178+
self.input = &self.input[..self.input.len() - 1];
179+
}
180+
181+
let file_len = (self.input[self.input.len() - 1] - b'0') as u32;
182+
self.pos -= file_len;
183+
self.id -= 1;
184+
self.input = &self.input[..self.input.len() - 1];
185+
186+
if file_len > 0 {
187+
return Some((self.pos, file_len, self.id));
188+
}
189+
}
190+
None
191+
}
192+
}
193+
194+
ReverseFileIterator {
195+
input: self.input.as_bytes(),
196+
pos: self.total_length,
197+
id: self.input.len().div_ceil(2) as u32,
198+
}
199+
}
200+
201+
#[inline]
202+
fn file_checksum(pos: u32, len: u32, id: u32) -> u64 {
203+
let pos_sum = len * (2 * pos + len - 1) / 2;
204+
pos_sum as u64 * id as u64
205+
}
206+
}
207+
208+
examples!(Day09<'_> -> (u64, u64) [
209+
{input: "2333133121414131402", part1: 1928, part2: 2858},
210+
]);

crates/year2024/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ utils::year!(2024 => year2024, ${
1010
6 => day06::Day06,
1111
7 => day07::Day07,
1212
8 => day08::Day08,
13+
9 => day09::Day09<'_>,
1314
10 => day10::Day10,
1415
});

0 commit comments

Comments
 (0)