Rust
use std::{cmp::Ordering, fs, str::FromStr};
use color_eyre::eyre::{Result, bail};
const DIAL_NUMBERS: isize = 100;
#[derive(Clone, Copy)]
struct Dial(usize);
impl Dial {
fn new() -> Self {
Self(50)
}
fn rotate(&mut self, rot: isize) -> usize {
let pass_0s;
let total = rot.wrapping_add_unsigned(self.0);
match total.cmp(&0) {
Ordering::Equal => {
pass_0s = 1;
self.0 = 0;
}
Ordering::Less => {
// Starting on 0 means we don't cross it
let started_0 = if self.0 == 0 { 1 } else { 0 };
pass_0s = 1 + (-total / DIAL_NUMBERS) as usize - started_0;
self.0 = (self.0 as isize + rot).rem_euclid(DIAL_NUMBERS) as usize;
}
Ordering::Greater => {
let full_turns = total / DIAL_NUMBERS;
pass_0s = full_turns as usize;
self.0 = (total - DIAL_NUMBERS * full_turns) as usize;
}
};
pass_0s
}
fn sequence(&mut self, s: &str) -> Result<(usize, usize)> {
let mut end_0s = 0;
let mut pass_0s = 0;
for l in s.lines() {
let num = isize::from_str(&l[1..]).unwrap();
let dir = l.bytes().next().unwrap();
pass_0s += match dir {
b'L' => self.rotate(-num),
b'R' => self.rotate(num),
_ => bail!("b{dir} is an invalid rotation direction"),
};
if self.0 == 0 {
end_0s += 1;
}
}
Ok((end_0s, pass_0s))
}
}
fn parts(filepath: &str) -> Result<(usize, usize)> {
let input = fs::read_to_string(filepath)?;
let mut dial = Dial::new();
let res = dial.sequence(&input)?;
Ok(res)
}
fn main() -> Result<()> {
color_eyre::install()?;
let (p1, p2) = parts("d01/input.txt")?;
println!("Part 1: {p1}");
println!("Part 2: {p2}");
Ok(())
}
I lost a lot of time struggling with edge cases in part two since I thought it was wasteful to run rem_euclid() (i.e. modulus) when I'd already got the information to do it more efficiently. While I got there in the end my final code was a bit ugly. This prettier, "unoptimised" version runs in 1.6ms, so I was being an idiot.