- 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