Day 22
This commit is contained in:
parent
ba21e5fdb0
commit
b83e13d0bb
4 changed files with 120 additions and 0 deletions
7
2024/day22/Cargo.lock
generated
Normal file
7
2024/day22/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 = "day22"
|
||||||
|
version = "0.1.0"
|
6
2024/day22/Cargo.toml
Normal file
6
2024/day22/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "day22"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
26
2024/day22/README.md
Normal file
26
2024/day22/README.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Solution Day 22
|
||||||
|
|
||||||
|
One of the few later ones I actually did on the day. The pseudo-random number generator is easy enough to
|
||||||
|
implement. If you read correctly, that is… I read "round to the nearest integer" first and was slightly off
|
||||||
|
with my numbers, until I saw that little "down" in there, which makes the whole thing _much_ easier. You don't
|
||||||
|
actually need to round, you can just do integer division, which will cut off the fractional part, leaving you
|
||||||
|
with the already rounded-down number.
|
||||||
|
|
||||||
|
## Task 1
|
||||||
|
|
||||||
|
After implementing the pseudo-random generator, it's easy enough to run it 2000x for each start number and
|
||||||
|
sum up the resulting numbers. Nothing special to see here.
|
||||||
|
|
||||||
|
## Task 2
|
||||||
|
|
||||||
|
This one was more complex. My initial naïve approach was to calculate all 4-digit sub-sequences, search for them
|
||||||
|
in all sequences and sum up the assiciated price. While this would probably yield the result _at some point_, the
|
||||||
|
complexity is _O(n² * m²)_ (with _n_ being the number of starting numbers and _m_ being the sequence length for each
|
||||||
|
starting number, assuming I got my maths right). Let's just say: not fun, definitely too slow.
|
||||||
|
|
||||||
|
My less naïve approach involves pre-calculating the first price of each sub-sequence for each starting number and
|
||||||
|
then afterwards going through them, summing them up grouped by sub-sequence and finding the maximum number of those
|
||||||
|
sums. This involves the heavy use of `HashMap`s (to keep track of the found sub-sequences and their associated number),
|
||||||
|
but brings complexity down to _ O(n * m)_ (for all three steps, actually. Pre-calculating the sequences and prices is _O(n * m)_,
|
||||||
|
calculating the sums and then finding the maximum is as well). This is then actually fast enough to finish before I
|
||||||
|
get bored.
|
81
2024/day22/src/main.rs
Normal file
81
2024/day22/src/main.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#![feature(map_try_insert)]
|
||||||
|
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
|
fn mix_and_prune(intermediate: i64, number: i64) -> i64 {
|
||||||
|
(intermediate ^ number) % 16777216
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_secret_number(number: i64) -> i64 {
|
||||||
|
let mut result = mix_and_prune(number * 64, number);
|
||||||
|
result = mix_and_prune(result / 32, result);
|
||||||
|
result = mix_and_prune(result * 2048, result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth_secret_number(start: i64, n: u32) -> i64 {
|
||||||
|
let mut result = start;
|
||||||
|
for _ in 0..n {
|
||||||
|
result = next_secret_number(result);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task1() {
|
||||||
|
let numbers = include_str!("../input.txt")
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.parse::<i64>().unwrap());
|
||||||
|
|
||||||
|
let sum: i64 = numbers.map(|number| nth_secret_number(number, 2000)).sum();
|
||||||
|
|
||||||
|
println!("Task 1: sum {sum}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_sequence(number: i64) -> HashMap<Vec<i64>, i64> {
|
||||||
|
let mut current_number = number;
|
||||||
|
let mut result = HashMap::new();
|
||||||
|
let mut current_pattern = VecDeque::new();
|
||||||
|
for _ in 0..2000 {
|
||||||
|
let next_number = next_secret_number(current_number);
|
||||||
|
let next_last_digit = next_number % 10;
|
||||||
|
let current_last_digit = current_number % 10;
|
||||||
|
current_pattern.push_back(next_last_digit - current_last_digit);
|
||||||
|
if current_pattern.len() == 4 {
|
||||||
|
// we're only ever interested in the first occurence of a pattern in the sequence, because that's what counts
|
||||||
|
let _ = result.try_insert(
|
||||||
|
Vec::from_iter(current_pattern.iter().cloned()),
|
||||||
|
next_last_digit,
|
||||||
|
);
|
||||||
|
current_pattern.pop_front(); // throw away the first digit. The pattern is a sliding window over the sequence
|
||||||
|
}
|
||||||
|
current_number = next_number;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task2() {
|
||||||
|
let numbers = include_str!("../input.txt")
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.parse::<i64>().unwrap());
|
||||||
|
|
||||||
|
let sequences: Vec<_> = numbers.map(calculate_sequence).collect();
|
||||||
|
|
||||||
|
let mut banana_counters = HashMap::new();
|
||||||
|
for sequence in sequences.iter() {
|
||||||
|
for (pattern, price) in sequence.iter() {
|
||||||
|
banana_counters.insert(
|
||||||
|
pattern,
|
||||||
|
banana_counters.get(pattern).unwrap_or(&0_i64) + price,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sum = banana_counters.values().max();
|
||||||
|
|
||||||
|
println!("Task 2: sum: {sum:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
task1();
|
||||||
|
task2();
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue