1use std::collections::VecDeque;
2use utils::prelude::*;
3
4#[derive(Clone, Debug)]
6pub struct Day09<'a> {
7 input: &'a str,
8 total_length: u32,
9}
10
11impl<'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 if file_len > 0 {
62 checksum += Self::file_checksum(file_pos, file_len, file_id);
63 }
64
65 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 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 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 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 break;
119 }
120 }
121
122 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
208examples!(Day09<'_> -> (u64, u64) [
209 {input: "2333133121414131402", part1: 1928, part2: 2858},
210]);