Advent 2024 Day 02
By EricMesa
- 7 minutes read - 1420 wordsTime once again to review my Advent of Code solutions! Also the latest die or dice from the Dice Envy Advent Calendar; scroll below the AoC code to see today’s die.
Advent of Code
Day 01
First off, a look back at yesterday as I was able to find time to attempt a solution in Rust.
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
use counter::Counter;
// The output is wrapped in a Result to allow matching on errors.
// Returns an Iterator to the Reader of the lines of the file.
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
fn main() {
// File hosts.txt must exist in the current path
if let Ok(lines) = read_lines("../../input.txt") {
let mut left_side: Vec<i64> = Vec::new();
let mut right_side: Vec<i64> = Vec::new();
// Consumes the iterator, returns an (Optional) String
for line in lines.flatten() {
let mut line_vals = line.split_whitespace();
left_side.push(line_vals.next().expect("Something went wrong").parse().expect("Something else went wrong?"));
right_side.push(line_vals.next().expect("Something went wrong").parse().expect("Something else went wrong?"));
}
left_side.sort();
right_side.sort();
let mut sum: Vec<i64> = Vec::new();
for n in 0..left_side.len(){
sum.push((right_side[n]-left_side[n]).abs())
}
let num_counts = right_side.iter().collect::<Counter<_>>();
let mut products: Vec<i64> = Vec::new();
for n in 0..left_side.len(){
products.push(left_side[n]*num_counts[&left_side[n]] as i64);
}
println!("The total distance is {}", sum.iter().sum::<i64>());
println!("The similarity is {}", products.iter().sum::<i64>())
}else { println!("no input.txt") }
}
For the input parsing I stole this from the Rust by Example page from the Rust developers. I also put it into my input parsing folder for the future. I find Rust to be like the parts of Haskell that I hate combined with the parts of non-scripting languages that I hate. It’s like the opposite of Go. See for example how I had to do 2 “expects” when populating the left and right side because I can’t guarantee the type (highlighted in the code above). I’m sure it’s part of what makes this such a great language for having less footguns than C or C++, but for something like Advent of Code, it keeps it from being a langauge I’ll probably be doing in real time. As I go back to work tomorrow, I expect to probably almost never do a Rust solution on the day of. Frankly, I’m not sure if I’ll do a Go solution in addition to my Python solution. It’s going to depend upon how things go and how much free time I have on any given night.
Day 02
Quick summary of the problem:
- Part 1: For each row of numbers, it’s valid if it either is only increasing or decreasing. And the delta between numbers is between 1 and 3 inclusive. Then sum how many are valid.
- Part 2: If a row was invalid, see if changing just one number makes it valid.
When I saw today’s puzzle before heading off to work, I immediately thought of 2021 Day 01. In 2021 Day 01 the most efficient way to do the problem was to use a sliding “window” to do the math. However, as I went swimming and had nothing else to think about but the puzzle, I realized there were a few complications compared to that puzzle from 3 years ago. First of all, all the numbers in a row need to be going in the same direction (either all increasing or all decreasing). Second, I’m not just figuring out the difference - I need to keep it constrained to between 1 and 3 inclusive.
My thought process for doing this is to tackle each constraint on its own. I don’t think this is the type of problem where that kind of inefficiency will cause issues. The second thing I was wondering if where I should stop early (as soon as I have an delta that’s outside of 1-3) or just wait until the end. Here’s the pseudo-code I’m considering:
Function 1: Parameter: a list of numbers (need to remember to cast them to integers) Create a sorted copy of the list. If the list matches we know they’re all increasing. Return True. Create a reserve sorted copy of the list (or reverse the currently sorted copy depending on the language). If the list matches we know they’re all decreasing. Return True. Function 2: Parameter: a list of numbers Maybe using window (see zip solution in 2021 Day 01 README) calculate the absolute value of the delta between adjacent numbers. If you want to break early (maybe the row has a LOT of numbers?) you’ll probably need a for loop. Otherwise, at least in Python, you can just put True/False into a list. Return the value of all(list). This will either be True if they’re all True (all are 1-3 delta) or False if ANY of them fail.
Main: Create placeholder variable (integer for languages that care) Loop over the rows. For each row If function1 AND function2 are true, add 1 to the placeholder variable.
Answer for part 1 is the placeholder variable value.
For part 2 I need to make both checks a little more complex. And I would need to make sure I’m only removing one of them. The only thing I can think of right now is the first check if I can fix the same direction bug and then fail on check delta. Then do the same in the other direction.
I was tearing my hair out trying to figure out why I kept coming up low, and it’s that I needed to return the modified list if I modified it to then test it against the other criteria.
After that I looked on reddit and found an edge case like [1, 2, 3, 4, 3] was causing failure. This was because I was too cavalier in part 1 about allowing same numbers in a row to pass the same direction test.
Here was my actual code:
# Solution to AoC 2024 Day 02: Red-Nosed Reports
from copy import deepcopy
def input_per_line(file: str):
"""This is for when each line is an input to the puzzle. The newline character is stripped."""
with open(file, 'r') as input_file:
return [line.rstrip() for line in input_file.readlines()]
def check_same_direction(numbers: list[int]) -> bool:
"""Check whether the numbers are all increasing or all decreasing and return true if they are.
This function will return True if there's a delta 0 between 2 numbers, but that will be caught in delta check."""
sorted_numbers = sorted(numbers)
reverse_sorted_numbers = sorted(numbers, reverse=True)
for (a, b) in zip(numbers, numbers[1:]):
if a == b:
return False
return True if sorted_numbers == numbers else reverse_sorted_numbers == numbers
def apply_dampener_to_same_direction(numbers: list[int]) -> (bool, list[int]):
"""Keeps checking if removing one number would fix the same direction error."""
checks = len(numbers)
if check_same_direction(numbers): # would have passed anyway
return True, numbers
for position in range(checks):
modified_numbers = [
number for pos, number in enumerate(numbers) if pos != position
]
if check_same_direction(modified_numbers):
return True, modified_numbers
return False, numbers
def apply_dampener_to_check_delta(numbers: list[int]) -> (bool, list[int]):
"""Keeps checking if removing one number would fix the same delta error."""
checks = len(numbers)
if check_delta(numbers): # would have passed anyway
return True, numbers
for position in range(checks):
modified_numbers = [
number for pos, number in enumerate(numbers) if pos != position
]
if check_delta(modified_numbers):
return True, modified_numbers
return False, numbers
def check_delta(numbers: list[int])->bool:
"""Check whether the delta between any 2 numbers is between 1-3 inclusive."""
deltas = []
for (a,b) in zip(numbers, numbers[1:]):
if abs(a-b) in [1,2,3]:
deltas.append(True)
else:
deltas.append(False)
return all(deltas)
def dampener(numbers: list[int])->bool:
(worked, new_list) = apply_dampener_to_same_direction(numbers)
if worked and check_delta(new_list):
return True
(worked, new_list) = apply_dampener_to_check_delta(numbers)
if worked and check_same_direction(new_list):
return True
return False
if __name__ == '__main__':
our_input = input_per_line("../input.txt")
safe_rows = 0
for row in our_input:
split_row = row.split()
split_row = [int(value) for value in split_row]
if check_same_direction(split_row) and check_delta(split_row):
safe_rows+=1
print(f"there are {safe_rows} safe reports.")
print("Applying dampener.....")
safe_dampened_rows = 0
for row in our_input:
split_row = row.split()
split_row = [int(value) for value in split_row]
if dampener(split_row):
safe_dampened_rows += 1
print(f"there are {safe_dampened_rows} safe reports with the dampener applied.")
Thinking about it again as I write up this blog post, I probably could have simplified things by modifying values and running both tests then instead of modifying values for inc/dec and delta tests separately. If I read this before attempting another language, I’ll give that a shot.
Dice Envy Dice
Today’s die was a beautiful, sparkly d8. I took the closeup shot with the flash on so that the sparkles in the die would really show up.
While today’s die is less explicitly Christmas-y, I think it still fits with the general sparkle and shine that comes along with Christmas.