- 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