From b9e2079de2136c2115bc8f994e28267c2409f42c Mon Sep 17 00:00:00 2001 From: Tobias Marschner Date: Sat, 21 Sep 2024 13:43:23 +0200 Subject: [PATCH] Add solutions for day20, parts one and two --- 2022/day20-part1/Cargo.toml | 11 +++ 2022/day20-part1/src/main.rs | 146 ++++++++++++++++++++++++++++++++ 2022/day20-part2/Cargo.toml | 11 +++ 2022/day20-part2/src/main.rs | 158 +++++++++++++++++++++++++++++++++++ 4 files changed, 326 insertions(+) create mode 100644 2022/day20-part1/Cargo.toml create mode 100644 2022/day20-part1/src/main.rs create mode 100644 2022/day20-part2/Cargo.toml create mode 100644 2022/day20-part2/src/main.rs diff --git a/2022/day20-part1/Cargo.toml b/2022/day20-part1/Cargo.toml new file mode 100644 index 0000000..0c727ec --- /dev/null +++ b/2022/day20-part1/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "day20-part1" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rayon = "1" +itertools = "0" +id-arena = { version = "2", features = ["rayon"] } diff --git a/2022/day20-part1/src/main.rs b/2022/day20-part1/src/main.rs new file mode 100644 index 0000000..91b04e0 --- /dev/null +++ b/2022/day20-part1/src/main.rs @@ -0,0 +1,146 @@ +use id_arena::{Arena, Id}; +use itertools::Itertools; + +type NodeId = Id; + +#[derive(Debug, Eq, PartialEq)] +struct Node { + value: i32, + prev: Option, + next: Option, +} + +fn main() { + // Use command line arguments to specify the input filename. + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + panic!("Usage: ./main \nNo input file provided. Exiting."); + } + + // Next, read the contents of the input file into a string for easier processing. + let input = std::fs::read_to_string(&args[1]).expect("Error opening file"); + + // The actual set of initial numbers, completely immutable. + let numbers = input + .lines() + .map(|e| e.parse::().unwrap()) + .collect_vec(); + + // Translate this list into a self-rolled linked-list with the help of the `id-arena` crate. + let mut nodes = Arena::::with_capacity(numbers.len()); + // Keep the original order of references for the mixing operation. + let mut og_order: Vec = Vec::with_capacity(numbers.len()); + + // Fill the arena with the Nodes. + let mut prev_node = None; + for num in numbers { + // Create the current Node. + let current_node = nodes.alloc(Node { + value: num, + prev: prev_node, + next: None, + }); + og_order.push(current_node); + // Fix the next-pointer of the previous node, if it exists. + if let Some(pn) = prev_node { + nodes[pn].next = Some(current_node); + } + // And make the current element the previous one. + prev_node = Some(current_node); + } + + // Make the linked list cyclic, as that matches the mixing behvaior of the task. + let first = og_order.first().copied().unwrap(); + let last = og_order.last().copied().unwrap(); + nodes[first].prev = Some(last); + nodes[last].next = Some(first); + + // We'll need it later: Find the element with value 0, it has special significance. + let zero_node = nodes + .iter() + .find_map(|(i, e)| if e.value == 0 { Some(i) } else { None }) + .unwrap(); + + let print_list = |a: &Arena, s: NodeId| { + print!("["); + let mut c = s; + loop { + print!("{}", a[c].value); + c = a[c].next.unwrap(); + if c == s { + break; + } + print!(", "); + } + println!("]"); + }; + + // Now iterate through all nodes in their original order. + for id in og_order { + // print_list(&nodes, zero_node); + + // Save the hassle if the value is 0. + if nodes[id].value == 0 { + continue; + } + + // Keep track of the element after which to insert the current one. + let mut target = nodes[id].prev.unwrap(); + // Move as many steps as the value requires. + for _ in 0..nodes[id].value.abs() { + // Move left or right, depending on sign of value. + if nodes[id].value.is_negative() { + // println!("Moving {} to the left.", nodes[id].value); + target = nodes[target].prev.unwrap(); + // Move again if the target is ourselves. + if target == id { + // println!("Skipping self, moving to the left."); + target = nodes[target].prev.unwrap(); + } + } else { + // println!("Moving {} to the right.", nodes[id].value); + target = nodes[target].next.unwrap(); + // Move again if the target is ourselves. + if target == id { + // println!("Skipping self, moving to the right."); + target = nodes[target].next.unwrap(); + } + } + } + + // Actually perform the swap. + let new_prev = target; + let new_next = nodes[target].next.unwrap(); + let old_prev = nodes[id].prev.unwrap(); + let old_next = nodes[id].next.unwrap(); + + // Rebind the old neighbors to each other. + nodes[old_prev].next = Some(old_next); + nodes[old_next].prev = Some(old_prev); + + // Bind the new neighbors to the node. + nodes[new_prev].next = Some(id); + nodes[new_next].prev = Some(id); + + // Bind the node itself to the new neighbors. + nodes[id].prev = Some(new_prev); + nodes[id].next = Some(new_next); + } + + // print_list(&nodes, zero_node); + + // Find the 1000th, 2000th and 3000th element, starting form the zero-element, + // and add them together for the final result. + let mut result = 0i32; + + let mut current_node = zero_node; + for i in 1..=3000 { + current_node = nodes[current_node].next.unwrap(); + if i % 1000 == 0 { + // println!("Adding {}.", nodes[current_node].value); + result += nodes[current_node].value; + } + } + + println!("Result: {}", result); +} diff --git a/2022/day20-part2/Cargo.toml b/2022/day20-part2/Cargo.toml new file mode 100644 index 0000000..c54d29c --- /dev/null +++ b/2022/day20-part2/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "day20-part2" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rayon = "1" +itertools = "0" +id-arena = { version = "2", features = ["rayon"] } diff --git a/2022/day20-part2/src/main.rs b/2022/day20-part2/src/main.rs new file mode 100644 index 0000000..b6d0aba --- /dev/null +++ b/2022/day20-part2/src/main.rs @@ -0,0 +1,158 @@ +use id_arena::{Arena, Id}; +use itertools::Itertools; + +type NodeId = Id; + +#[derive(Debug, Eq, PartialEq)] +struct Node { + value: i64, + prev: Option, + next: Option, +} + +fn main() { + // Use command line arguments to specify the input filename. + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + panic!("Usage: ./main \nNo input file provided. Exiting."); + } + + // Next, read the contents of the input file into a string for easier processing. + let input = std::fs::read_to_string(&args[1]).expect("Error opening file"); + + // The actual set of initial numbers, completely immutable. + // Here the "decryption key" is multiplied into the list. + let numbers = input + .lines() + // .map(|e| e.parse::().unwrap()) + .map(|e| e.parse::().unwrap() * 811589153) + .collect_vec(); + + // Translate this list into a self-rolled linked-list with the help of the `id-arena` crate. + let mut nodes = Arena::::with_capacity(numbers.len()); + // Keep the original order of references for the mixing operation. + let mut og_order: Vec = Vec::with_capacity(numbers.len()); + + // Fill the arena with the Nodes. + let mut prev_node = None; + for num in numbers { + // Create the current Node. + let current_node = nodes.alloc(Node { + value: num, + prev: prev_node, + next: None, + }); + og_order.push(current_node); + // Fix the next-pointer of the previous node, if it exists. + if let Some(pn) = prev_node { + nodes[pn].next = Some(current_node); + } + // And make the current element the previous one. + prev_node = Some(current_node); + } + + // Make the linked list cyclic, as that matches the mixing behvaior of the task. + let first = og_order.first().copied().unwrap(); + let last = og_order.last().copied().unwrap(); + nodes[first].prev = Some(last); + nodes[last].next = Some(first); + + // We'll need it later: Find the element with value 0, it has special significance. + let zero_node = nodes + .iter() + .find_map(|(i, e)| if e.value == 0 { Some(i) } else { None }) + .unwrap(); + + let print_list = |a: &Arena, s: NodeId| { + print!("["); + let mut c = s; + loop { + print!("{}", a[c].value); + c = a[c].next.unwrap(); + if c == s { + break; + } + print!(", "); + } + println!("]"); + }; + + // Now iterate through all nodes in their original order - ten times (for some reason). + for (i, id) in (0..10).cartesian_product(og_order.iter().copied()) { + // print!("Iteration {} / ", i); + // print_list(&nodes, zero_node); + + // Save the hassle if the value is 0. + if nodes[id].value == 0 { + continue; + } + + let move_value = nodes[id].value; + let mut move_amount = move_value.abs(); + let move_reverse = move_value.is_negative(); + + // Automatically wrap moves larger than the entire list. + // Subtract one since the list over which we move does not contain the element we're + // moving - conceptually it's a list with 4999 elements, not 5000. + move_amount %= (nodes.len() as i64) - 1; + + // Keep track of the element after which to insert the current one. + let mut target = nodes[id].prev.unwrap(); + // Move as many steps as the value requires. + for _ in 0..move_amount { + // Move left or right, depending on sign of value. + if move_reverse { + // println!("Moving {} to the left.", nodes[id].value); + target = nodes[target].prev.unwrap(); + // Move again if the target is ourselves. + if target == id { + // println!("Skipping self, moving to the left."); + target = nodes[target].prev.unwrap(); + } + } else { + // println!("Moving {} to the right.", nodes[id].value); + target = nodes[target].next.unwrap(); + // Move again if the target is ourselves. + if target == id { + // println!("Skipping self, moving to the right."); + target = nodes[target].next.unwrap(); + } + } + } + + // Actually perform the swap. + let new_prev = target; + let new_next = nodes[target].next.unwrap(); + let old_prev = nodes[id].prev.unwrap(); + let old_next = nodes[id].next.unwrap(); + + // Rebind the old neighbors to each other. + nodes[old_prev].next = Some(old_next); + nodes[old_next].prev = Some(old_prev); + + // Bind the new neighbors to the node. + nodes[new_prev].next = Some(id); + nodes[new_next].prev = Some(id); + + // Bind the node itself to the new neighbors. + nodes[id].prev = Some(new_prev); + nodes[id].next = Some(new_next); + } + + // print_list(&nodes, zero_node); + + // Find the 1000th, 2000th and 3000th element, starting form the zero-element, + // and add them together for the final result. + let mut result = 0i64; + + let mut current_node = zero_node; + for i in 1..=3000 { + current_node = nodes[current_node].next.unwrap(); + if i % 1000 == 0 { + // println!("Adding {}.", nodes[current_node].value); + result += nodes[current_node].value; + } + } + + println!("Result: {}", result); +}