day 7
This commit is contained in:
		
							parent
							
								
									c44da97678
								
							
						
					
					
						commit
						ce9038a90e
					
				
					 4 changed files with 190 additions and 0 deletions
				
			
		
							
								
								
									
										7
									
								
								2024/day7/Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								2024/day7/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 = "day7"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
							
								
								
									
										4
									
								
								2024/day7/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								2024/day7/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "day7"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2021"
 | 
				
			||||||
							
								
								
									
										34
									
								
								2024/day7/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								2024/day7/README.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					# Solution Day 7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Calculating expression and testing whether we can make them equal on both sides. Not too hard.
 | 
				
			||||||
 | 
					Parsing the input is rather easy with a bit of `.split()` and `.parse()`. Algorithm for testing the
 | 
				
			||||||
 | 
					expressions is of the brute-force variety: generate all possible combinations of operators, calculate
 | 
				
			||||||
 | 
					the expression for each combination and see if it matches the result. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Task 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This one was easy at first: all possible combinations of operators (since there are only 2) can be generated
 | 
				
			||||||
 | 
					by counting between `0` and `pow(2, size)` (where `size` is the number of operators we need to fill the equation)
 | 
				
			||||||
 | 
					and treating each number as a bit mask, where the bit in position `n` represents an operator (`Add` if the bit
 | 
				
			||||||
 | 
					is `0`, `Multiply` if it is `1`). This was easy enough to implement and I even got it right on the first try.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Task 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I kind of had the inkling, that my simple "bitmask" solution for task 1 would get me into trouble with task 2. And
 | 
				
			||||||
 | 
					wouldn't you know? It did. Three operators cannot be easily expressed with a binary number (more on that in a bit).
 | 
				
			||||||
 | 
					I thought about implementing symbolic calculations (i.e. representing the number as `Vec<usize>` and doing the
 | 
				
			||||||
 | 
					counting by hand, including overflow and everything), but that seemed too much effort. The crate `itertools` contains
 | 
				
			||||||
 | 
					a `.combinations()` method, which can be made to work like this: `[Add, Add, Multiply, Multiply, Concat, Concat].combinations(2)`
 | 
				
			||||||
 | 
					(not the actual syntax, but you get the idea). This will generate all possible length 2 combination of the operators.
 | 
				
			||||||
 | 
					Works, but is _exceedingly_ slow, so I abandoned that approach. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					My actual solution goes back to my teaching days at uni: calculating the digits of a number in arbitrary base (in
 | 
				
			||||||
 | 
					this case: base 3). To get the next digit at the back, calculate `number % base`. Then continue with `number /= base` to
 | 
				
			||||||
 | 
					get the number without the last digit and continue until you run out of digits (or, in my case: until you've generate enough
 | 
				
			||||||
 | 
					digits corresponding to the operators needed).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Another tricky bit was the implementation of the `||` operator. Since the whole implementation is done numerically, it
 | 
				
			||||||
 | 
					_should_ be implemented as `left * pow(10, round(log10(right))) + right` (basically: multiply with 10 ^ number of right digits to 
 | 
				
			||||||
 | 
					get enough 0s at the end and add the number on top). While this works in theory, there is probably a precision or something, 
 | 
				
			||||||
 | 
					since `ceil` and `log10` only work in `f64` instead of `u64`. I consistently got the wrong results. In the end I gave up and
 | 
				
			||||||
 | 
					used a combination of `format!()` and `.parse()` to concatenate in string-space. 
 | 
				
			||||||
							
								
								
									
										145
									
								
								2024/day7/src/main.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								2024/day7/src/main.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,145 @@
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					struct Expression {
 | 
				
			||||||
 | 
					    result: u64,
 | 
				
			||||||
 | 
					    operands: Vec<u64>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Expression {
 | 
				
			||||||
 | 
					    fn calculate(&self, operators: &Vec<Ops>) -> u64 {
 | 
				
			||||||
 | 
					        let mut result = self.operands[0];
 | 
				
			||||||
 | 
					        for idx in 0..operators.len() {
 | 
				
			||||||
 | 
					            match operators[idx] {
 | 
				
			||||||
 | 
					                Ops::Add => result += self.operands[idx + 1],
 | 
				
			||||||
 | 
					                Ops::Multiply => result *= self.operands[idx + 1],
 | 
				
			||||||
 | 
					                Ops::Concat => {
 | 
				
			||||||
 | 
					                    result = format!("{result}{}", self.operands[idx + 1])
 | 
				
			||||||
 | 
					                        .parse()
 | 
				
			||||||
 | 
					                        .expect("Concat result must be parsable as a number")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        result
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<&str> for Expression {
 | 
				
			||||||
 | 
					    fn from(value: &str) -> Self {
 | 
				
			||||||
 | 
					        let mut parts = value.split(":");
 | 
				
			||||||
 | 
					        let result = parts
 | 
				
			||||||
 | 
					            .next()
 | 
				
			||||||
 | 
					            .expect("Expected result before :")
 | 
				
			||||||
 | 
					            .parse()
 | 
				
			||||||
 | 
					            .expect("Expected result to be number");
 | 
				
			||||||
 | 
					        let operands: Vec<u64> = parts
 | 
				
			||||||
 | 
					            .next()
 | 
				
			||||||
 | 
					            .expect("Expected operands after :")
 | 
				
			||||||
 | 
					            .split(" ")
 | 
				
			||||||
 | 
					            .filter_map(|op| op.parse().ok())
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
 | 
					        return Self { result, operands };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
 | 
					enum Ops {
 | 
				
			||||||
 | 
					    Add,
 | 
				
			||||||
 | 
					    Multiply,
 | 
				
			||||||
 | 
					    Concat,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					struct OperatorGenerator {
 | 
				
			||||||
 | 
					    size: u32,
 | 
				
			||||||
 | 
					    base: usize,
 | 
				
			||||||
 | 
					    current_combination: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl OperatorGenerator {
 | 
				
			||||||
 | 
					    fn new(size: u32, base: usize) -> OperatorGenerator {
 | 
				
			||||||
 | 
					        OperatorGenerator {
 | 
				
			||||||
 | 
					            size,
 | 
				
			||||||
 | 
					            base,
 | 
				
			||||||
 | 
					            current_combination: 0,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Iterator for OperatorGenerator {
 | 
				
			||||||
 | 
					    type Item = Vec<Ops>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn next(&mut self) -> Option<Self::Item> {
 | 
				
			||||||
 | 
					        if self.current_combination == usize::pow(self.base, self.size) {
 | 
				
			||||||
 | 
					            None
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            let mut current = self.current_combination;
 | 
				
			||||||
 | 
					            self.current_combination += 1;
 | 
				
			||||||
 | 
					            let mut ops = vec![];
 | 
				
			||||||
 | 
					            for idx in 0..self.size {
 | 
				
			||||||
 | 
					                let remainder = current % self.base;
 | 
				
			||||||
 | 
					                current /= self.base;
 | 
				
			||||||
 | 
					                match remainder {
 | 
				
			||||||
 | 
					                    0 => ops.push(Ops::Add),
 | 
				
			||||||
 | 
					                    1 => ops.push(Ops::Multiply),
 | 
				
			||||||
 | 
					                    2 => ops.push(Ops::Concat),
 | 
				
			||||||
 | 
					                    _ => panic!("Don't support more than 3 operators"),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Some(ops)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn read_input() -> Vec<Expression> {
 | 
				
			||||||
 | 
					    let input = std::fs::read_to_string("./input.txt").unwrap();
 | 
				
			||||||
 | 
					    input
 | 
				
			||||||
 | 
					        .lines()
 | 
				
			||||||
 | 
					        .filter(|line| line.len() > 0)
 | 
				
			||||||
 | 
					        .map(|line| line.into())
 | 
				
			||||||
 | 
					        .collect()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn test_expression(exp: &Expression, with_concat: bool) -> bool {
 | 
				
			||||||
 | 
					    let generator = OperatorGenerator::new(
 | 
				
			||||||
 | 
					        exp.operands.len() as u32 - 1,
 | 
				
			||||||
 | 
					        if with_concat { 3 } else { 2 },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    for operators in generator {
 | 
				
			||||||
 | 
					        if exp.calculate(&operators) == exp.result {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn task1() {
 | 
				
			||||||
 | 
					    let expressions = read_input();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let sum = expressions.iter().fold(0, |sum, exp| {
 | 
				
			||||||
 | 
					        if test_expression(exp, false) {
 | 
				
			||||||
 | 
					            sum + exp.result
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            sum
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    println!("Task 1: Sum: {sum}");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn task2() {
 | 
				
			||||||
 | 
					    let expressions = read_input();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let sum = expressions.iter().fold(0, |sum, exp| {
 | 
				
			||||||
 | 
					        if test_expression(exp, true) {
 | 
				
			||||||
 | 
					            sum + exp.result
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            sum
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    println!("Task 2: Sum: {sum}");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    task1();
 | 
				
			||||||
 | 
					    task2();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue