advent-of-code/2022/day13-part2/src/main.rs

167 lines
5.6 KiB
Rust

use std::{cmp::Ordering, iter::zip, str::Chars};
#[derive(Debug)]
enum Packet {
Number(i32),
List(Vec<Packet>),
}
impl Packet {
// Parse a single packet line into the Packet data structure.
fn from_string(s: &mut Chars) -> Vec<Packet> {
// println!("Entering with {}", s.clone().collect::<String>());
// We're going to iterate through this character by character.
// It is assumed that the opening '[' is already stripped.
// Create an empty vector packet to fill in a loop.
let mut packet_list: Vec<Packet> = Vec::new();
// Now iterate through all the remaining characters.
loop {
// Collect a single element, i.e. all characters until the first occurence of '[', ']' or ','.
// The delimiter itself will be collected into `c`.
let mut elem = String::new();
let mut c: char;
loop {
c = s.next().unwrap();
if c == '[' || c == ']' || c == ',' {
break;
} else {
elem.push(c);
}
}
// Parse the element if it isn't empty and add it to the list.
if !elem.is_empty() {
let num = Packet::Number(elem.parse().unwrap());
packet_list.push(num);
}
// Encountering a new list?
// Call recursively and collect everything there.
if c == '[' {
let sublist = Packet::from_string(s);
packet_list.push(Packet::List(sublist));
}
// Encountered the end of the list?
// Then we're done here.
if c == ']' {
break;
}
}
packet_list
}
}
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");
// --- TASK BEGIN ---
// Collect all actual packets into a big packet vector.
let mut packets: Vec<_> = input
.lines()
.filter(|l| !l.is_empty())
.map(|l| Packet::from_string(&mut l[1..].chars()))
.collect();
// Create copies of the divider packages and add them to the big vector.
packets.push(vec![Packet::List(vec![Packet::Number(2)])]);
packets.push(vec![Packet::List(vec![Packet::Number(6)])]);
// The magic line. Sort the vector using `packet_compare`.
packets.sort_unstable_by(packet_compare);
// Find the indices of the 2 and 6 divider.
let idx2 = packets.iter().enumerate().find(|(_, x)| {
packet_compare(
x,
&vec![Packet::List(vec![Packet::Number(2)])],
).is_eq()
}).unwrap().0;
let idx6 = packets.iter().enumerate().find(|(_, x)| {
packet_compare(
x,
&vec![Packet::List(vec![Packet::Number(6)])],
).is_eq()
}).unwrap().0;
print_packet_list(&packets);
println!("Decoder key: {}", (idx2 + 1) * (idx6 + 1));
}
fn print_packet_list(packet_list: &Vec<Vec<Packet>>) {
for packet in packet_list {
print_packet(packet);
// Add newlines after every "top-level" packet.
println!();
}
}
fn print_packet(packet: &Vec<Packet>) {
print!("[");
for e in packet {
match e {
Packet::Number(x) => {
print!("{x},");
}
Packet::List(l) => {
print_packet(l);
}
}
}
print!("]");
}
fn packet_compare(left: &Vec<Packet>, right: &Vec<Packet>) -> Ordering {
let mut result: Ordering;
let mut zipper = zip(left, right);
loop {
match zipper.next() {
Some(d) => {
// Determine the Ordering result for the next two "elements", whatever they may be.
result = match d {
(Packet::Number(lv), Packet::Number(rv)) => {
// Compare the integers directly.
lv.cmp(rv)
}
(Packet::Number(lv), Packet::List(rl)) => {
// Left is a number, right is a list.
// Convert the number to a list and then compare those.
let ll: Vec<Packet> = vec![Packet::Number(*lv)];
packet_compare(&ll, rl)
}
(Packet::List(ll), Packet::Number(rv)) => {
// Left is a list, right is a number.
// Convert the number to a list and then compare those.
let rl: Vec<Packet> = vec![Packet::Number(*rv)];
packet_compare(ll, &rl)
}
(Packet::List(ll), Packet::List(rl)) => {
// Recursively step into the lists.
packet_compare(ll, rl)
}
};
}
None => {
// Zipper empty?
// We need to remember which of the lists ran out first.
result = left.len().cmp(&right.len());
// And definitely break, ofc, since the zipper is done.
break;
}
}
// Have we reached a conclusion already? If so, return.
if result != Ordering::Equal {
break;
}
}
result
}