Day 9
This commit is contained in:
parent
9562e5024b
commit
5f58d7b95b
4 changed files with 170 additions and 0 deletions
7
2024/day9/Cargo.lock
generated
Normal file
7
2024/day9/Cargo.lock
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "day9"
|
||||
version = "0.1.0"
|
6
2024/day9/Cargo.toml
Normal file
6
2024/day9/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "day9"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
31
2024/day9/README.md
Normal file
31
2024/day9/README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Solution Day 9
|
||||
|
||||
Disk defragmentation... That's a thing I haven't seen in a long while. That brings back memories of sitting in front of a text
|
||||
representation of my hard disk, seeing the little squares jump back and forth when the defragmentation program started to cluster related blocks together...
|
||||
The fragmentation here is somewhat simpler, than the real ones back then and the stakes are also somewhat lower. No risk
|
||||
of screwing up data, when you accidentally crash.
|
||||
|
||||
## Task 1
|
||||
The approach is rather simple: run two pointers from the front and the back of the disk. Whenever the back pointer encounters an occupied block,
|
||||
run the front pointer until you find an empty block, copy over the contents from the back end remove them there. Stop when the two
|
||||
pointers meet in the middle. Afterwards calculate the checksum.
|
||||
|
||||
## Task 2
|
||||
A bit more involved. My approach searches a file block from the back, counts its length, searches an empty block from the front
|
||||
to find enough free space and moves the data over. Initially I managed to have an endless loop whenever a block could not be copied
|
||||
at all, because the back pointer searches for the next non-free block. Since some files couldn't be moved out of the way, the back
|
||||
pointer would immediately find the same file again and try to copy it, failing again and the whole cycle started over. This is
|
||||
easily solved by the
|
||||
|
||||
```rust
|
||||
else {
|
||||
movable_file_idx -= 1; // step before the file that we were unable to move
|
||||
}
|
||||
```
|
||||
|
||||
block in line 106ff, which steps just _before_ the file we looked at last. This time we stop when we reach file 0, because this
|
||||
cannot ever be moved (on account of being at the start of the disk already).
|
||||
|
||||
This approach has complexity _O(n²)_, which can probably be massively improved (e.g. by finding all empty blocks in one pass,
|
||||
recording their positions and sizes in a `HashMap` and then using the _O(1)_ lookups to find a suitable free block, when making the second
|
||||
pass to find the actual file blocks. This would bring the complexity to _O(n)_, if I'm not mistaken.)
|
126
2024/day9/src/main.rs
Normal file
126
2024/day9/src/main.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use std::usize;
|
||||
|
||||
type BlockId = usize;
|
||||
type Disk = Vec<Option<BlockId>>;
|
||||
|
||||
fn load_input() -> Disk {
|
||||
let input = std::fs::read_to_string("input.txt").expect("Should be able to read input file");
|
||||
// let input = "2333133121414131402";
|
||||
|
||||
input
|
||||
.chars()
|
||||
.enumerate()
|
||||
.flat_map(|(idx, ch)| {
|
||||
let is_empty = idx % 2 == 1;
|
||||
if let Some(num) = ch.to_digit(10) {
|
||||
let id = idx / 2;
|
||||
vec![if is_empty { None } else { Some(id) }; num as usize]
|
||||
} else {
|
||||
vec![] // ignoring invalid characters in the input (at the end)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn print_disk(d: &Disk) {
|
||||
for ch in d {
|
||||
if let Some(ch) = ch {
|
||||
print!("{}", ch);
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
println!("");
|
||||
}
|
||||
|
||||
fn task1() {
|
||||
let mut disk = load_input();
|
||||
// print_disk(&disk);
|
||||
|
||||
let mut free_idx = 0;
|
||||
let mut last_block_idx = disk.len() - 1;
|
||||
|
||||
while free_idx < last_block_idx {
|
||||
// find the first free block
|
||||
while free_idx < last_block_idx && disk[free_idx].is_some() {
|
||||
free_idx += 1;
|
||||
}
|
||||
// find the last used block
|
||||
while last_block_idx > free_idx && disk[last_block_idx].is_none() {
|
||||
last_block_idx -= 1;
|
||||
}
|
||||
|
||||
// found something to swap
|
||||
if free_idx < last_block_idx {
|
||||
disk[free_idx] = disk[last_block_idx];
|
||||
disk[last_block_idx] = None;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the checksum
|
||||
let checksum = disk.iter().enumerate().fold(0, |checksum, (idx, block)| {
|
||||
if let Some(value) = block {
|
||||
checksum + idx * value
|
||||
} else {
|
||||
checksum
|
||||
}
|
||||
});
|
||||
|
||||
println!("Task 1: checksum: {checksum}");
|
||||
}
|
||||
|
||||
fn task2() {
|
||||
let mut disk = load_input();
|
||||
|
||||
let mut movable_file_idx = disk.len() - 1;
|
||||
let mut current_file_id;
|
||||
|
||||
while movable_file_idx > 0 {
|
||||
// find the end of the next file
|
||||
while movable_file_idx > 0 && disk[movable_file_idx].is_none() {
|
||||
movable_file_idx -= 1;
|
||||
}
|
||||
current_file_id = disk[movable_file_idx];
|
||||
// find the start of the file
|
||||
let mut file_length = 0;
|
||||
while movable_file_idx > 0 && disk[movable_file_idx] == current_file_id {
|
||||
movable_file_idx -= 1;
|
||||
file_length += 1;
|
||||
}
|
||||
movable_file_idx += 1; // one step back because the while loop move _past_ the start of the file
|
||||
// find block from the start, that is big enough for the file
|
||||
let target_idx = 'space_search: {
|
||||
for idx in 0..movable_file_idx {
|
||||
if (0..file_length).all(|target_offset| disk[idx + target_offset].is_none()) {
|
||||
break 'space_search idx;
|
||||
}
|
||||
}
|
||||
movable_file_idx
|
||||
};
|
||||
if target_idx < movable_file_idx {
|
||||
// found a block, move the file
|
||||
for offset in 0..file_length {
|
||||
disk[target_idx + offset] = disk[movable_file_idx + offset];
|
||||
disk[movable_file_idx + offset] = None;
|
||||
}
|
||||
} else {
|
||||
movable_file_idx -= 1; // step before the file that we were unable to move
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the checksum
|
||||
let checksum = disk.iter().enumerate().fold(0, |checksum, (idx, block)| {
|
||||
if let Some(value) = block {
|
||||
checksum + idx * value
|
||||
} else {
|
||||
checksum
|
||||
}
|
||||
});
|
||||
|
||||
println!("Task 2: checksum: {checksum}");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
task1();
|
||||
task2();
|
||||
}
|
Loading…
Add table
Reference in a new issue