advent-of-code/2022/day20-part1/src/main.rs

147 lines
4.8 KiB
Rust

use id_arena::{Arena, Id};
use itertools::Itertools;
type NodeId = Id<Node>;
#[derive(Debug, Eq, PartialEq)]
struct Node {
value: i32,
prev: Option<NodeId>,
next: Option<NodeId>,
}
fn main() {
// Use command line arguments to specify the input filename.
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
panic!("Usage: ./main <input-file>\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::<i32>().unwrap())
.collect_vec();
// Translate this list into a self-rolled linked-list with the help of the `id-arena` crate.
let mut nodes = Arena::<Node>::with_capacity(numbers.len());
// Keep the original order of references for the mixing operation.
let mut og_order: Vec<NodeId> = 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<Node>, 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);
}