- Day 5
This commit is contained in:
parent
22168572c8
commit
7abbc37a65
4 changed files with 171 additions and 0 deletions
7
2024/day5/Cargo.lock
generated
Normal file
7
2024/day5/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 = "day5"
|
||||||
|
version = "0.1.0"
|
6
2024/day5/Cargo.toml
Normal file
6
2024/day5/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "day5"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
37
2024/day5/README.md
Normal file
37
2024/day5/README.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Solution Day 5
|
||||||
|
|
||||||
|
Slightly more complex file format this time. It consists of two parts: "rules" or the form `x|y` and "updates", that look like `x,y,z,...`.
|
||||||
|
Luckily for parsing we can just go through the `.lines()` iterator one by one, check whether the line contains a `|` and, if so, parse
|
||||||
|
it into a rule struct. If not, we just continue splitting it into `Vec<u32>`. We can do this the simple way consuming each line to look
|
||||||
|
for the `|`, because there's an empty line between the blocks, that we can just throw away.
|
||||||
|
|
||||||
|
After everything is parsed, the "real" work starts: writing a rule-interpreter, that checks, whether the updates are correct by the rules.
|
||||||
|
The solution is rather simple: for each element we check, whether any element, that should come _after_ it, actually comes before and the
|
||||||
|
other way around. If this isn't the case, the update is correct.
|
||||||
|
|
||||||
|
While the rule-checker is reasonably simple, it _does_ need to go through all rules twice for each element of the update. This could be
|
||||||
|
improved by indexing the rules in a `HashMap`based on their constituents and do an `O(1)` lookup. The runtime is so fast, however, that
|
||||||
|
this is not worth the effort.
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
|
||||||
|
This task is easy: find all correct updates (just a filter with the rule interpreter) and sum up the middle elements. Since all updates are
|
||||||
|
of odd length, the middle element always exists, so `.reduce()` can do quick work.
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
|
||||||
|
Slightly more complex: the *in*correct rules should be retrieved and fixed, before the middle element is supposed to be summed up. Took
|
||||||
|
me a while, but then I had a bright idea: the rules all together are nothing more than an ordering. If you interpret all rules against a
|
||||||
|
pair of update entries, you can tell which way around they are supposed to go, i.e. which one is less than the other. So we can use
|
||||||
|
the `.sort_by()` function from the standard library with a custom comparison function, that interprets all relevant rules and returns the
|
||||||
|
ordering. After sorting the elements, summing up the middle is the same as in task 1.
|
||||||
|
|
||||||
|
_Note:_ This assumes, that the full set of rules represents a total order. If it were to be a partial order, the resulting order would be
|
||||||
|
unspecified. Luckily for us, the designers made the ruleset a total order.
|
||||||
|
|
||||||
|
This could again be more efficient, by indexing the rules once first and then do an `O(1)` lookup for all relevant rules instead of checking
|
||||||
|
_all_ rules for each call to the comparator. Again, the runtime is so quick, that this seems unnecessary optimization.
|
||||||
|
|
||||||
|
Also, there's a `.clone()` for the update vector in there, because I couldn't figure out how to tell the borrow checker, that I would be
|
||||||
|
happy to fully consume the input while checking it. There's no need for me to touch any update twice, so borrowing isn't actually necessary.
|
||||||
|
I just didn't find the right combination of mutable vectors and `IntoIterator`s to make this clear to the borrow checker.
|
121
2024/day5/src/main.rs
Normal file
121
2024/day5/src/main.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
struct PageOrderingRule {
|
||||||
|
first: u32,
|
||||||
|
second: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for PageOrderingRule {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
let mut parts = s.split("|");
|
||||||
|
Self {
|
||||||
|
first: parts
|
||||||
|
.next()
|
||||||
|
.expect("Missing first rule part")
|
||||||
|
.parse()
|
||||||
|
.expect("Cannot parse first rule part"),
|
||||||
|
second: parts
|
||||||
|
.next()
|
||||||
|
.expect("Missing second rule part")
|
||||||
|
.parse()
|
||||||
|
.expect("Cannot parse second rule part"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_correct(update: &Vec<u32>, rules: &Vec<PageOrderingRule>) -> bool {
|
||||||
|
for idx in 0..update.len() {
|
||||||
|
let entry = update[idx];
|
||||||
|
let before_rules = rules.iter().filter(|rule| rule.first == entry);
|
||||||
|
let after_rules = rules.iter().filter(|rule| rule.second == entry);
|
||||||
|
|
||||||
|
// none of the elements before the checked one should come after it
|
||||||
|
let before_correct = before_rules.clone().all(|rule| {
|
||||||
|
update[..idx]
|
||||||
|
.iter()
|
||||||
|
.all(|&candidate| candidate != rule.second)
|
||||||
|
});
|
||||||
|
// none of the elements after the checked one should actually come before it
|
||||||
|
let after_correct = after_rules.clone().all(|rule| {
|
||||||
|
update[idx..]
|
||||||
|
.iter()
|
||||||
|
.all(|&candidate| candidate != rule.first)
|
||||||
|
});
|
||||||
|
if !before_correct || !after_correct {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_input() -> (Vec<PageOrderingRule>, Vec<Vec<u32>>) {
|
||||||
|
let data = std::fs::read_to_string("./input.txt").expect("Cannot read file");
|
||||||
|
let mut rules: Vec<PageOrderingRule> = vec![];
|
||||||
|
let mut lines = data.lines();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match lines.next() {
|
||||||
|
None => break,
|
||||||
|
Some(text) => {
|
||||||
|
if text.contains("|") {
|
||||||
|
rules.push(text.into());
|
||||||
|
} else {
|
||||||
|
break; // no longer a rule. We've moved to the next part of the file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let updates: Vec<Vec<u32>> = lines
|
||||||
|
.map(|line| {
|
||||||
|
line.split(",")
|
||||||
|
.map(|entry| entry.parse().expect("Cannot parse entry"))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
(rules, updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task1() {
|
||||||
|
let (rules, updates) = load_input();
|
||||||
|
let correct_updates = updates.iter().filter(|update| is_correct(&update, &rules));
|
||||||
|
let sum = correct_updates.fold(0, |existing, update| existing + update[update.len() / 2]);
|
||||||
|
|
||||||
|
println!("Task 1: Sum: {sum}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task2() {
|
||||||
|
let (rules, updates) = load_input();
|
||||||
|
let incorrect_updates = updates
|
||||||
|
.iter()
|
||||||
|
.filter(|&update| !is_correct(&update, &rules));
|
||||||
|
|
||||||
|
let corrected_updates = incorrect_updates.into_iter().map(|update| {
|
||||||
|
let mut copy = update.clone(); // TODO don't like the clone here, but sort_by requires a mutable reference
|
||||||
|
copy.sort_by(|a, b| {
|
||||||
|
// basically evaluate every rule to sort the thing
|
||||||
|
for rule in rules.iter() {
|
||||||
|
if rule.first == *a && rule.second == *b {
|
||||||
|
// correct order
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
if rule.first == *b && rule.second == *a {
|
||||||
|
// wrong way around
|
||||||
|
return Ordering::Greater;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ordering::Equal
|
||||||
|
});
|
||||||
|
copy
|
||||||
|
});
|
||||||
|
|
||||||
|
let sum = corrected_updates.fold(0, |existing, update| existing + update[update.len() / 2]);
|
||||||
|
|
||||||
|
println!("Task 2: Sum: {sum}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
task1();
|
||||||
|
task2();
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue