~sgeisenh/aoc

23d1593e5d655b4014892907cd183de2e1be0a3d — Samuel Eisenhandler 9 months ago 96ae00a
Progressss
A 2018/rust/Cargo.lock => 2018/rust/Cargo.lock +84 -0
@@ 0,0 1,84 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"

[[package]]
name = "day_02"
version = "0.1.0"

[[package]]
name = "day_03"
version = "0.1.0"
dependencies = [
 "anyhow",
]

[[package]]
name = "day_04"
version = "0.1.0"
dependencies = [
 "anyhow",
]

[[package]]
name = "day_05"
version = "0.1.0"
dependencies = [
 "anyhow",
]

[[package]]
name = "day_06"
version = "0.1.0"
dependencies = [
 "anyhow",
]

[[package]]
name = "day_07"
version = "0.1.0"
dependencies = [
 "anyhow",
]

[[package]]
name = "day_08"
version = "0.1.0"
dependencies = [
 "anyhow",
]

[[package]]
name = "day_09"
version = "0.1.0"
dependencies = [
 "anyhow",
]

[[package]]
name = "day_10"
version = "0.1.0"
dependencies = [
 "anyhow",
]

[[package]]
name = "day_11"
version = "0.1.0"

[[package]]
name = "day_12"
version = "0.1.0"

[[package]]
name = "day_13"
version = "0.1.0"
dependencies = [
 "anyhow",
]

A 2018/rust/Cargo.toml => 2018/rust/Cargo.toml +16 -0
@@ 0,0 1,16 @@
[workspace]

members = [
    "day_02",
    "day_03",
    "day_04",
    "day_05",
    "day_06",
    "day_07",
    "day_08",
    "day_09",
    "day_10",
    "day_11",
    "day_12",
    "day_13",
]

D 2018/rust/day_02/Cargo.lock => 2018/rust/day_02/Cargo.lock +0 -7
@@ 1,7 0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "day_02"
version = "0.1.0"

A 2018/rust/day_03/Cargo.toml => 2018/rust/day_03/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_03"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_03/src/main.rs => 2018/rust/day_03/src/main.rs +150 -0
@@ 0,0 1,150 @@
use std::str::FromStr;

use anyhow::anyhow;

const INPUT: &str = include_str!("../../../inputs/03.txt");

#[derive(Debug, PartialEq, Eq)]
struct Claim {
    id: u32,
    x: u32,
    y: u32,
    width: u32,
    height: u32,
}

impl FromStr for Claim {
    type Err = anyhow::Error;

    /// Parse a claim from a string.
    ///
    /// Example:
    /// #1 @ 1,3: 4x4
    /// #2 @ 3,1: 4x4
    /// #3 @ 5,5: 2x2
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let or_else = || anyhow!("Invalid claim string");
        let mut parts = s.split_whitespace();
        let id = parts
            .next()
            .ok_or_else(or_else)?
            .trim_start_matches('#')
            .parse()?;
        let _ = parts.next().ok_or_else(or_else)?; // Skip @
        let mut coords = parts
            .next()
            .ok_or_else(or_else)?
            .trim_end_matches(':')
            .split(',');
        let x = coords.next().ok_or_else(or_else)?.parse()?;
        let y = coords.next().ok_or_else(or_else)?.parse()?;
        let mut size = parts.next().ok_or_else(or_else)?.split('x');
        let width = size.next().ok_or_else(or_else)?.parse()?;
        let height = size.next().ok_or_else(or_else)?.parse()?;
        Ok(Claim {
            id,
            x,
            y,
            width,
            height,
        })
    }
}

struct Fabric(Box<[u32]>);

impl Fabric {
    fn new() -> Self {
        Fabric(vec![0; 1000 * 1000].into_boxed_slice())
    }

    fn add_claim(&mut self, claim: &Claim) {
        for x in claim.x..claim.x + claim.width {
            for y in claim.y..claim.y + claim.height {
                self.0[(x + y * 1000) as usize] += 1;
            }
        }
    }

    fn count_overlaps(&self) -> u32 {
        self.0.iter().filter(|&&x| x > 1).count() as u32
    }

    fn check_claim(&self, claim: &Claim) -> bool {
        for x in claim.x..claim.x + claim.width {
            for y in claim.y..claim.y + claim.height {
                if self.0[(x + y * 1000) as usize] > 1 {
                    return false;
                }
            }
        }
        true
    }
}

fn part_one(input: &str) -> anyhow::Result<u32> {
    let mut fabric = Fabric::new();
    for line in input.lines() {
        let claim = Claim::from_str(line)?;
        fabric.add_claim(&claim);
    }
    Ok(fabric.count_overlaps())
}

fn part_two(input: &str) -> anyhow::Result<u32> {
    let mut fabric = Fabric::new();
    let claims = input
        .lines()
        .map(Claim::from_str)
        .collect::<Result<Vec<_>, _>>()?;
    for claim in &claims {
        fabric.add_claim(claim);
    }
    claims
        .iter()
        .find(|claim| fabric.check_claim(claim))
        .map(|claim| claim.id)
        .ok_or_else(|| anyhow!("No claim found"))
}

fn main() -> anyhow::Result<()> {
    println!("Part one: {}", part_one(INPUT)?);
    println!("Part two: {}", part_two(INPUT)?);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_claim_from_str() {
        let claim = Claim::from_str("#1 @ 1,3: 4x4").unwrap();
        assert_eq!(
            claim,
            Claim {
                id: 1,
                x: 1,
                y: 3,
                width: 4,
                height: 4,
            }
        );
    }

    #[test]
    fn test_part_one() {
        let input = r#"#1 @ 1,3: 4x4
#2 @ 3,1: 4x4
#3 @ 5,5: 2x2"#;
        assert_eq!(part_one(input).unwrap(), 4);
    }

    #[test]
    fn test_part_two() {
        let input = r#"#1 @ 1,3: 4x4
#2 @ 3,1: 4x4
#3 @ 5,5: 2x2"#;
        assert_eq!(part_two(input).unwrap(), 3);
    }
}

A 2018/rust/day_04/Cargo.toml => 2018/rust/day_04/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_04"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_04/src/main.rs => 2018/rust/day_04/src/main.rs +276 -0
@@ 0,0 1,276 @@
use std::{collections::HashMap, str::FromStr};

const INPUT: &str = include_str!("../../../inputs/04.txt");

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct LogTimestamp {
    year: u16,
    month: u8,
    day: u8,
    hour: u8,
    minute: u8,
}

impl FromStr for LogTimestamp {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut words = s.split_whitespace();
        let date = words
            .next()
            .ok_or_else(|| anyhow::anyhow!("Missing date"))?;
        let time = words
            .next()
            .ok_or_else(|| anyhow::anyhow!("Missing time"))?;
        let date = date.trim_start_matches('[').trim_end_matches(']');
        let time = time.trim_end_matches(']');
        let (year, month, day) = {
            let mut date = date.split('-');
            (
                date.next()
                    .ok_or_else(|| anyhow::anyhow!("Missing year"))?
                    .parse()?,
                date.next()
                    .ok_or_else(|| anyhow::anyhow!("Missing month"))?
                    .parse()?,
                date.next()
                    .ok_or_else(|| anyhow::anyhow!("Missing day"))?
                    .parse()?,
            )
        };
        let (hour, minute) = {
            let mut time = time.split(':');
            (
                time.next()
                    .ok_or_else(|| anyhow::anyhow!("Missing hour"))?
                    .parse()?,
                time.next()
                    .ok_or_else(|| anyhow::anyhow!("Missing minute"))?
                    .parse()?,
            )
        };
        Ok(LogTimestamp {
            year,
            month,
            day,
            hour,
            minute,
        })
    }
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum LogEvent {
    BeginShift(u16),
    FallAsleep,
    WakeUp,
}

impl FromStr for LogEvent {
    type Err = anyhow::Error;

    // Guard #10 begins shift
    // falls asleep
    // wakes up
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut words = s.split_whitespace();
        match words
            .next()
            .ok_or_else(|| anyhow::anyhow!("Missing log event"))?
        {
            "Guard" => {
                let id = words
                    .next()
                    .ok_or_else(|| anyhow::anyhow!("Missing guard id"))?
                    .trim_start_matches('#')
                    .parse()?;
                Ok(LogEvent::BeginShift(id))
            }
            "falls" => Ok(LogEvent::FallAsleep),
            "wakes" => Ok(LogEvent::WakeUp),
            _ => Err(anyhow::anyhow!("Invalid log event")),
        }
    }
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct LogRecord {
    timestamp: LogTimestamp,
    event: LogEvent,
}

impl FromStr for LogRecord {
    type Err = anyhow::Error;

    // [1518-11-01 00:00] Guard #10 begins shift
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut words = s.split_whitespace();
        // The timestamp is the first two words
        let timestamp = words
            .by_ref()
            .take(2)
            .collect::<Vec<_>>()
            .join(" ")
            .parse()?;
        let event = words.collect::<Vec<_>>().join(" ").parse()?;
        Ok(LogRecord { timestamp, event })
    }
}

struct GuardRecord([u32; 60]);

impl GuardRecord {
    fn new() -> Self {
        Self([0; 60])
    }

    fn add_sleep(&mut self, start: &LogTimestamp, end: &LogTimestamp) {
        for minute in start.minute..end.minute {
            self.0[minute as usize] += 1;
        }
    }

    fn total_sleep(&self) -> u32 {
        self.0.iter().sum()
    }

    fn most_slept_minute(&self) -> (usize, &u32) {
        self.0
            .iter()
            .enumerate()
            .max_by_key(|(_, &count)| count)
            .unwrap_or((0, &0))
    }
}

fn create_guard_records_from_input(input: &str) -> anyhow::Result<HashMap<u16, GuardRecord>> {
    let mut records = input
        .lines()
        .map(|line| line.parse::<LogRecord>())
        .collect::<Result<Vec<_>, _>>()?;
    records.sort_unstable();
    let mut guard_records: HashMap<u16, GuardRecord> = HashMap::new();
    let mut sleep_start: Option<LogTimestamp> = None;
    let mut sleep_minutes: &mut GuardRecord = &mut GuardRecord::new();
    for record in records {
        match record.event {
            LogEvent::BeginShift(id) => {
                sleep_minutes = guard_records
                    .entry(id)
                    .or_insert_with(GuardRecord::new);
            }
            LogEvent::FallAsleep => sleep_start = Some(record.timestamp),
            LogEvent::WakeUp => {
                let sleep_end = record.timestamp;
                let Some(ref start) = sleep_start else {
                    return Err(anyhow::anyhow!("Missing sleep start"));
                };
                sleep_minutes.add_sleep(start, &sleep_end);
            }
        }
    }
    Ok(guard_records)
}

fn part_one(input: &str) -> anyhow::Result<u32> {
    let guard_records = create_guard_records_from_input(input)?;
    let (id, record) = guard_records
        .iter()
        .max_by_key(|(_, record)| record.total_sleep())
        .ok_or_else(|| anyhow::anyhow!("Missing guard records"))?;
    Ok((*id as u32) * (record.most_slept_minute().0 as u32))
}

fn part_two(input: &str) -> anyhow::Result<u32> {
    let guard_records = create_guard_records_from_input(input)?;
    let (id, record) = guard_records
        .iter()
        .max_by_key(|(_, record)| record.most_slept_minute().1)
        .ok_or_else(|| anyhow::anyhow!("Missing guard records"))?;
    Ok((*id as u32) * (record.most_slept_minute().0 as u32))
}

fn main() -> anyhow::Result<()> {
    println!("Part One: {}", part_one(INPUT)?);
    println!("Part Two: {}", part_two(INPUT)?);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_log_timestamp() {
        assert_eq!(
            "[1518-11-01 00:00]".parse::<LogTimestamp>().unwrap(),
            LogTimestamp {
                year: 1518,
                month: 11,
                day: 1,
                hour: 0,
                minute: 0,
            }
        );
    }

    #[test]
    fn test_parse_log_event() {
        assert_eq!(
            "Guard #10 begins shift".parse::<LogEvent>().unwrap(),
            LogEvent::BeginShift(10)
        );
        assert_eq!(
            "falls asleep".parse::<LogEvent>().unwrap(),
            LogEvent::FallAsleep
        );
        assert_eq!("wakes up".parse::<LogEvent>().unwrap(), LogEvent::WakeUp);
    }

    #[test]
    fn test_parse_log_record() {
        assert_eq!(
            "[1518-11-01 00:00] Guard #10 begins shift"
                .parse::<LogRecord>()
                .unwrap(),
            LogRecord {
                timestamp: LogTimestamp {
                    year: 1518,
                    month: 11,
                    day: 1,
                    hour: 0,
                    minute: 0,
                },
                event: LogEvent::BeginShift(10),
            }
        );
    }

    const TEST_INPUT: &str = r#"[1518-11-01 00:00] Guard #10 begins shift
[1518-11-01 00:05] falls asleep
[1518-11-01 00:25] wakes up
[1518-11-01 00:30] falls asleep
[1518-11-01 00:55] wakes up
[1518-11-01 23:58] Guard #99 begins shift
[1518-11-02 00:40] falls asleep
[1518-11-02 00:50] wakes up
[1518-11-03 00:05] Guard #10 begins shift
[1518-11-03 00:24] falls asleep
[1518-11-03 00:29] wakes up
[1518-11-04 00:02] Guard #99 begins shift
[1518-11-04 00:36] falls asleep
[1518-11-04 00:46] wakes up
[1518-11-05 00:03] Guard #99 begins shift
[1518-11-05 00:45] falls asleep
[1518-11-05 00:55] wakes up"#;

    #[test]
    fn test_part_one() {
        assert_eq!(part_one(TEST_INPUT).unwrap(), 240);
    }

    #[test]
    fn test_part_two() {
        assert_eq!(part_two(TEST_INPUT).unwrap(), 4455);
    }
}

A 2018/rust/day_05/Cargo.toml => 2018/rust/day_05/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_05"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_05/src/main.rs => 2018/rust/day_05/src/main.rs +57 -0
@@ 0,0 1,57 @@
const INPUT: &str = include_str!("../../../inputs/05.txt");

fn react(polymer: &str) -> String {
    let mut new_polymer = String::new();
    for c in polymer.chars() {
        if let Some(last) = new_polymer.pop() {
            if last != c && last.to_ascii_lowercase() == c.to_ascii_lowercase() {
                continue;
            }
            new_polymer.push(last);
        }
        new_polymer.push(c);
    }
    new_polymer
}

fn part_one(input: &str) -> usize {
    react(input.trim()).len()
}

fn part_two(input: &str) -> anyhow::Result<usize> {
    let polymer = input.trim();
    let alphabet = "abcdefghijklmnopqrstuvwxyz";
    alphabet
        .chars()
        .map(|c| {
            let new_polymer = polymer.replace([c, c.to_ascii_uppercase()], "");
            react(&new_polymer).len()
        })
        .min()
        .ok_or_else(|| anyhow::anyhow!("No minimum found"))
}

fn main() -> anyhow::Result<()> {
    println!("Part One: {}", part_one(INPUT));
    println!("Part Two: {}", part_two(INPUT)?);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_part_one() {
        assert_eq!(part_one("aA"), 0);
        assert_eq!(part_one("abBA"), 0);
        assert_eq!(part_one("abAB"), 4);
        assert_eq!(part_one("aabAAB"), 6);
        assert_eq!(part_one("dabAcCaCBAcCcaDA"), 10);
    }

    #[test]
    fn test_part_two() {
        assert_eq!(part_two("dabAcCaCBAcCcaDA").unwrap(), 4);
    }
}

A 2018/rust/day_06/Cargo.toml => 2018/rust/day_06/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_06"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_06/src/main.rs => 2018/rust/day_06/src/main.rs +135 -0
@@ 0,0 1,135 @@
const INPUT: &str = include_str!("../../../inputs/06.txt");

fn find_closest_coordinate(x: usize, y: usize, coordinates: &[(usize, usize)]) -> Option<usize> {
    let mut closest = None;
    let mut closest_distance = std::usize::MAX;
    for (i, (cx, cy)) in coordinates.iter().enumerate() {
        let distance = cx.abs_diff(x) + cy.abs_diff(y);
        match distance.cmp(&closest_distance) {
            std::cmp::Ordering::Less => {
                closest = Some(i);
                closest_distance = distance;
            }
            std::cmp::Ordering::Equal => {
                closest = None;
            }
            std::cmp::Ordering::Greater => {}
        }
    }
    closest
}

fn parse_coordinates(input: &str) -> anyhow::Result<Vec<(usize, usize)>> {
    input
        .lines()
        .map(str::trim)
        .map(|line| -> anyhow::Result<(usize, usize)> {
            let mut parts = line.split(", ");
            let y = parts
                .next()
                .ok_or_else(|| anyhow::anyhow!("No x coordinate"))?
                .parse::<usize>()?;
            let x = parts
                .next()
                .ok_or_else(|| anyhow::anyhow!("No y coordinate"))?
                .parse::<usize>()?;
            Ok((x, y))
        })
        .collect()
}

fn part_one(input: &str) -> anyhow::Result<usize> {
    let coordinates = parse_coordinates(input)?;
    let width = coordinates.iter().map(|(x, _)| x).max().unwrap_or(&0) + 5;
    let height = coordinates.iter().map(|(_, y)| y).max().unwrap_or(&0) + 5;
    let mut grid = vec![vec![None; width]; height];
    for (i, row) in grid.iter_mut().enumerate() {
        for (j, e) in row.iter_mut().enumerate() {
            *e = find_closest_coordinate(i, j, &coordinates);
        }
    }
    // The infinite coordinates are those that are closest to the edges of the grid.
    let mut infinite = vec![];
    for row in grid.iter_mut() {
        if let Some(Some(e)) = row.first() {
            if !infinite.contains(e) {
                infinite.push(*e);
            }
        }
        if let Some(Some(e)) = row.last() {
            if !infinite.contains(e) {
                infinite.push(*e);
            }
        }
    }
    for e in grid.first().unwrap_or(&vec![]).iter().flatten() {
        if !infinite.contains(e) {
            infinite.push(*e);
        }
    }
    for e in grid.last().unwrap_or(&vec![]).iter().flatten() {
        if !infinite.contains(e) {
            infinite.push(*e);
        }
    }
    let mut areas = vec![0; coordinates.len()];
    for row in grid {
        for e in row.into_iter().flatten() {
            areas[e] += 1;
        }
    }
    Ok(*areas
        .iter()
        .enumerate()
        .filter(|(idx, _)| !infinite.contains(idx))
        .map(|(_, e)| e)
        .max()
        .unwrap_or(&0))
}

fn part_two(input: &str, total_distance: u32) -> anyhow::Result<usize> {
    let coordinates = parse_coordinates(input)?;
    let width = coordinates.iter().map(|(x, _)| x).max().unwrap_or(&0) + 1;
    let height = coordinates.iter().map(|(_, y)| y).max().unwrap_or(&0) + 1;
    let mut total = 0;
    for i in 0..width {
        for j in 0..height {
            let coord_total = coordinates
                .iter()
                .map(|(x, y)| (x.abs_diff(i) + y.abs_diff(j)) as u32)
                .sum::<u32>();
            if coord_total < total_distance {
                total += 1;
            }
        }
    }
    Ok(total)
}

fn main() -> anyhow::Result<()> {
    println!("Part one: {}", part_one(INPUT)?);
    println!("Part two: {}", part_two(INPUT, 10000)?);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    const TEST_INPUT: &str = r#"1, 1
1, 6
8, 3
3, 4
5, 5
8, 9"#;

    #[test]
    fn test_part_one() {
        assert_eq!(part_one(TEST_INPUT).unwrap(), 17);
    }

    #[test]
    fn test_part_two() {
        assert_eq!(part_two(TEST_INPUT, 32).unwrap(), 16);
    }
}

A 2018/rust/day_07/Cargo.toml => 2018/rust/day_07/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_07"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_07/src/main.rs => 2018/rust/day_07/src/main.rs +232 -0
@@ 0,0 1,232 @@
use std::{
    cmp::Reverse,
    collections::{BTreeMap, BTreeSet, BinaryHeap},
    str::FromStr,
};

const INPUT: &str = include_str!("../../../inputs/07.txt");

// E.g. Step C must be finished before step A can begin.
#[derive(Debug)]
struct Dependency {
    letter: char,
    before: char,
}

impl FromStr for Dependency {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut words = s.split_whitespace();
        let before = words
            .nth(1)
            .ok_or(anyhow::anyhow!("No nth(1)"))?
            .chars()
            .next()
            .ok_or(anyhow::anyhow!("No nth(1).chars().next()"))?;
        let letter = words
            .nth(5)
            .ok_or(anyhow::anyhow!("No nth(4)"))?
            .chars()
            .next()
            .ok_or(anyhow::anyhow!("No nth(4).chars().next()"))?;
        Ok(Dependency { letter, before })
    }
}

fn parse_input(input: &str) -> anyhow::Result<Vec<Dependency>> {
    input
        .lines()
        .map(|line| line.parse::<Dependency>())
        .collect()
}

#[derive(Debug)]
struct DependencyGraph {
    // Map from letter to the letters that must be completed before it.
    dependencies: BTreeMap<char, BTreeSet<char>>,

    // Map from letter to the letters that must be completed after it.
    dependents: BTreeMap<char, BTreeSet<char>>,
}

impl DependencyGraph {
    fn new() -> Self {
        Self {
            dependencies: BTreeMap::new(),
            dependents: BTreeMap::new(),
        }
    }

    fn from_dependencies(dependencies: &[Dependency]) -> Self {
        let mut graph = Self::new();
        for dependency in dependencies {
            graph.add_dependency(dependency);
        }
        graph
    }

    fn add_dependency(&mut self, dependency: &Dependency) {
        self.dependencies
            .entry(dependency.letter)
            .or_insert_with(BTreeSet::new)
            .insert(dependency.before);
        self.dependents
            .entry(dependency.before)
            .or_insert_with(BTreeSet::new)
            .insert(dependency.letter);
    }

    fn remove_dependency(&mut self, dependency: &Dependency) {
        self.dependencies
            .entry(dependency.letter)
            .and_modify(|set| {
                set.remove(&dependency.before);
            });
        self.dependents.entry(dependency.before).and_modify(|set| {
            set.remove(&dependency.letter);
        });
    }

    fn get_dependencies(&self, letter: char) -> impl Iterator<Item = &char> {
        self.dependencies.get(&letter).into_iter().flatten()
    }

    fn get_dependents(&self, letter: char) -> impl Iterator<Item = &char> {
        self.dependents.get(&letter).into_iter().flatten()
    }
}

fn part_one(input: &str) -> anyhow::Result<String> {
    // Parse input
    let dependencies = parse_input(input)?;
    // Get all letters
    let letters: BTreeSet<char> = dependencies
        .iter()
        .flat_map(|dependency| [dependency.letter, dependency.before])
        .collect();
    // Assemble a graph of dependencies
    let mut graph = DependencyGraph::from_dependencies(&dependencies);
    let mut frontier: BTreeSet<char> = letters
        .iter()
        .filter(|letter| graph.get_dependencies(**letter).next().is_none())
        .copied()
        .collect();
    let mut result = String::new();
    while let Some(node) = frontier.pop_first() {
        result.push(node);
        let dependents: Vec<char> = graph.get_dependents(node).copied().collect();
        for dependent in dependents {
            graph.remove_dependency(&Dependency {
                letter: dependent,
                before: node,
            });
            if graph.get_dependencies(dependent).next().is_none() {
                frontier.insert(dependent);
            }
        }
    }
    Ok(result)
}

struct WorkQueue {
    num_workers: usize,
    base_time: usize,
    priority_queue: BinaryHeap<(Reverse<usize>, char)>,
}

impl WorkQueue {
    fn new(num_workers: usize, base_time: usize) -> Self {
        Self {
            num_workers,
            base_time,
            priority_queue: BinaryHeap::new(),
        }
    }

    fn push(&mut self, start_time: usize, node: char) {
        let completion_time = start_time + self.base_time + (node as usize) - ('A' as usize) + 1;
        self.priority_queue.push((Reverse(completion_time), node));
    }

    fn pop(&mut self) -> Option<(char, usize)> {
        self.priority_queue
            .pop()
            .map(|(Reverse(completion_time), node)| (node, completion_time))
    }

    fn fill_queue(&mut self, frontier: &mut BTreeSet<char>, start_time: usize) {
        while self.priority_queue.len() < self.num_workers {
            if let Some(node) = frontier.pop_first() {
                self.push(start_time, node);
            } else {
                break;
            }
        }
    }
}

fn part_two(input: &str, num_workers: usize, base_time: usize) -> anyhow::Result<usize> {
    // Parse input
    let dependencies = parse_input(input)?;
    // Get all letters
    let letters: BTreeSet<char> = dependencies
        .iter()
        .flat_map(|dependency| [dependency.letter, dependency.before])
        .collect();
    // Assemble a graph of dependencies
    let mut graph = DependencyGraph::from_dependencies(&dependencies);
    let mut frontier: BTreeSet<char> = letters
        .iter()
        .filter(|letter| graph.get_dependencies(**letter).next().is_none())
        .copied()
        .collect();
    // Nodes and the time they will be completed.
    let mut work_queue = WorkQueue::new(num_workers, base_time);
    work_queue.fill_queue(&mut frontier, 0);
    let mut result = 0;
    while let Some((node, completion_time)) = work_queue.pop() {
        result = completion_time;
        let dependents: Vec<char> = graph.get_dependents(node).copied().collect();
        for dependent in dependents {
            graph.remove_dependency(&Dependency {
                letter: dependent,
                before: node,
            });
            if graph.get_dependencies(dependent).next().is_none() {
                frontier.insert(dependent);
            }
        }
        work_queue.fill_queue(&mut frontier, completion_time);
    }
    Ok(result)
}

fn main() -> anyhow::Result<()> {
    println!("Part one: {}", part_one(INPUT)?);
    println!("Part two: {}", part_two(INPUT, 5, 60)?);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    const TEST_INPUT: &str = r#"Step C must be finished before step A can begin.
    Step C must be finished before step F can begin.
    Step A must be finished before step B can begin.
    Step A must be finished before step D can begin.
    Step B must be finished before step E can begin.
    Step D must be finished before step E can begin.
    Step F must be finished before step E can begin."#;

    #[test]
    fn test_part_one() {
        assert_eq!(part_one(TEST_INPUT).unwrap(), "CABDFE");
    }

    #[test]
    fn test_part_two() {
        assert_eq!(part_two(TEST_INPUT, 2, 0).unwrap(), 15);
    }
}

A 2018/rust/day_08/Cargo.toml => 2018/rust/day_08/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_08"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_08/src/main.rs => 2018/rust/day_08/src/main.rs +97 -0
@@ 0,0 1,97 @@
use std::str::FromStr;

use anyhow::Result;

#[derive(Debug)]
struct Node {
    children: Box<[Node]>,
    metadata: Box<[i32]>,
}

impl Node {
    fn from_numbers(numbers: &[i32]) -> Result<(Node, &[i32])> {
        let (children_count, metadata_count, mut numbers) = match *numbers {
            [children_count, metadata_count, ref rest @ ..] => {
                (children_count as usize, metadata_count as usize, rest)
            }
            _ => anyhow::bail!("Not enough numbers"),
        };
        let children = (0..children_count)
            .map(|_| {
                let (child, rest) = Node::from_numbers(numbers)?;
                numbers = rest;
                Ok(child)
            })
            .collect::<Result<_>>()?;
        let (metadata, numbers) = numbers.split_at(metadata_count);
        let metadata = metadata.into();
        Ok((Node { children, metadata }, numbers))
    }

    fn sum_metadata(&self) -> i32 {
        self.metadata.iter().sum::<i32>()
            + self.children.iter().map(|c| c.sum_metadata()).sum::<i32>()
    }

    fn value(&self) -> i32 {
        if self.children.is_empty() {
            self.sum_metadata()
        } else {
            self.metadata
                .iter()
                .filter_map(|&i| self.children.get(i as usize - 1))
                .map(|c| c.value())
                .sum::<i32>()
        }
    }
}

impl FromStr for Node {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let numbers = s
            .split_whitespace()
            .map(|s| Ok(s.parse::<i32>()?))
            .collect::<Result<Vec<_>>>()?;
        let (node, rest) = Node::from_numbers(&numbers)?;
        if !rest.is_empty() {
            anyhow::bail!("Didn't consume all numbers");
        }
        Ok(node)
    }
}

fn part_one(input: &str) -> Result<i32> {
    let node = input.parse::<Node>()?;
    Ok(node.sum_metadata())
}

fn part_two(input: &str) -> Result<i32> {
    let node = input.parse::<Node>()?;
    Ok(node.value())
}

fn main() -> Result<()> {
    const INPUT: &str = include_str!("../../../inputs/08.txt");
    println!("Part one: {}", part_one(INPUT)?);
    println!("Part two: {}", part_two(INPUT)?);
    Ok(())
}

#[cfg(test)]
mod test {
    use super::*;

    const TEST_INPUT: &str = "2 3 0 3 10 11 12 1 1 0 1 99 2 1 1 2";

    #[test]
    fn test_part_one() {
        assert_eq!(part_one(TEST_INPUT).unwrap(), 138);
    }

    #[test]
    fn test_part_two() {
        assert_eq!(part_two(TEST_INPUT).unwrap(), 66);
    }
}

A 2018/rust/day_09/Cargo.toml => 2018/rust/day_09/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_09"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_09/src/main.rs => 2018/rust/day_09/src/main.rs +82 -0
@@ 0,0 1,82 @@
use std::collections::VecDeque;
use std::str::FromStr;

use anyhow::Result;
use anyhow::anyhow;

#[derive(Debug)]
struct Scenario {
    players: usize,
    last_marble: usize,
}

impl FromStr for Scenario {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let mut parts = s.split_whitespace();
        let players = parts.next().ok_or_else(|| anyhow!("No players"))?.parse()?;
        let last_marble = parts.nth(5).ok_or_else(|| anyhow!("No last marble"))?.parse()?;
        Ok(Scenario { players, last_marble })
    }
}

impl Scenario {
    fn high_score(&self, scale: usize) -> Result<usize> {
        let last_marble = self.last_marble * scale;
        let mut circle = {
            let mut circle = VecDeque::with_capacity(last_marble);
            circle.push_back(1);
            circle.push_back(0);
            circle
        };
        let mut scores = vec![0; self.players];
        for marble in 2..=last_marble {
            if marble % 23 == 0 {
                circle.rotate_right(7);
                let removed = circle.pop_front().ok_or_else(|| anyhow!("No marble to remove"))?;
                scores[marble % self.players] += marble + removed;
            } else {
                circle.rotate_left(2);
                circle.push_front(marble);
            }
        }
        Ok(*scores.iter().max().ok_or_else(|| anyhow!("No max score"))?)
    }
}

fn part_one(input: &str) -> Result<usize> {
    input.parse::<Scenario>()?.high_score(1)
}

fn part_two(input: &str) -> Result<usize> {
    input.parse::<Scenario>()?.high_score(100)
}

fn main() -> Result<()> {
    const INPUT: &str = include_str!("../../../inputs/09.txt");
    println!("Part one: {}", part_one(INPUT)?);
    println!("Part two: {}", part_two(INPUT)?);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    const TEST_CASES: &[(&str, usize)] = &[
        ("9 players; last marble is worth 25 points", 32),
        ("10 players; last marble is worth 1618 points", 8317),
        ("13 players; last marble is worth 7999 points", 146373),
        ("17 players; last marble is worth 1104 points", 2764),
        ("21 players; last marble is worth 6111 points", 54718),
        ("30 players; last marble is worth 5807 points", 37305),
    ];

    #[test]
    fn test_part_one() {
        for (input, expected) in TEST_CASES {
            assert_eq!(part_one(input).unwrap(), *expected);
        }
    }
}
\ No newline at end of file

A 2018/rust/day_10/Cargo.toml => 2018/rust/day_10/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_10"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_10/src/main.rs => 2018/rust/day_10/src/main.rs +200 -0
@@ 0,0 1,200 @@
use std::{fmt::Display, str::FromStr};

use anyhow::Result;

#[derive(Debug, Clone, Copy)]
struct LightPoint {
    x: i32,
    y: i32,
    dx: i32,
    dy: i32,
}

impl FromStr for LightPoint {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut parts = s.split(|c| c == '<' || c == '>' || c == ',');
        let x = parts
            .nth(1)
            .ok_or(anyhow::anyhow!("No x"))?
            .trim()
            .parse()?;
        let y = parts
            .next()
            .ok_or(anyhow::anyhow!("No y"))?
            .trim()
            .parse()?;
        let dx = parts
            .nth(1)
            .ok_or(anyhow::anyhow!("No dx"))?
            .trim()
            .parse()?;
        let dy = parts
            .next()
            .ok_or(anyhow::anyhow!("No dy"))?
            .trim()
            .parse()?;
        Ok(LightPoint { x, y, dx, dy })
    }
}

impl LightPoint {
    fn step(&mut self) {
        self.x += self.dx;
        self.y += self.dy;
    }
}

#[derive(Debug, Clone)]
struct Grid(Vec<LightPoint>);

impl FromStr for Grid {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let points = s
            .lines()
            .map(|line| line.trim().parse())
            .collect::<Result<Vec<_>, _>>()?;
        Ok(Grid(points))
    }
}

impl Display for Grid {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Ok((min_x, max_x, min_y, max_y)) = self.bounding_rect() else {
            return Ok(());
        };
        let mut grid = vec![vec!['.'; (max_x - min_x + 1) as usize]; (max_y - min_y + 1) as usize];
        for point in &self.0 {
            grid[(point.y - min_y) as usize][(point.x - min_x) as usize] = '#';
        }
        for (idx, row) in grid.iter().enumerate() {
            write!(f, "{}", row.iter().collect::<String>())?;
            if idx < grid.len() - 1 {
                writeln!(f)?;
            }
        }
        Ok(())
    }
}

impl Grid {
    fn step(&mut self) {
        for point in &mut self.0 {
            point.step();
        }
    }

    fn bounding_rect(&self) -> Result<(i32, i32, i32, i32)> {
        let min_x = self
            .0
            .iter()
            .map(|p| p.x)
            .min()
            .ok_or_else(|| anyhow::anyhow!("No min_x"))?;
        let max_x = self
            .0
            .iter()
            .map(|p| p.x)
            .max()
            .ok_or_else(|| anyhow::anyhow!("No max_x"))?;
        let min_y = self
            .0
            .iter()
            .map(|p| p.y)
            .min()
            .ok_or_else(|| anyhow::anyhow!("No min_y"))?;
        let max_y = self
            .0
            .iter()
            .map(|p| p.y)
            .max()
            .ok_or_else(|| anyhow::anyhow!("No max_y"))?;
        Ok((min_x, max_x, min_y, max_y))
    }

    fn spread(&self) -> Result<i32> {
        let (min_x, max_x, min_y, max_y) = self.bounding_rect()?;
        Ok((max_x - min_x).abs() + (max_y - min_y).abs())
    }
}

fn solve(input: &str) -> Result<(String, i32)> {
    let mut grid: Grid = input.parse()?;
    let mut seconds = 0;
    let mut min_grid = grid.clone();
    let mut min_seconds = 0;
    for _ in 0..50000 {
        grid.step();
        seconds += 1;
        if grid.spread()? < min_grid.spread()? {
            min_grid = grid.clone();
            min_seconds = seconds;
        }
    }
    Ok((min_grid.to_string(), min_seconds))
}

fn main() -> Result<()> {
    const INPUT: &str = include_str!("../../../inputs/10.txt");
    let (part_1, part_2) = solve(INPUT)?;
    println!("Part 1:\n{}", part_1);
    println!("Part 2: {}", part_2);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    const TEST_INPUT: &str = r#"position=< 9,  1> velocity=< 0,  2>
    position=< 7,  0> velocity=<-1,  0>
    position=< 3, -2> velocity=<-1,  1>
    position=< 6, 10> velocity=<-2, -1>
    position=< 2, -4> velocity=< 2,  2>
    position=<-6, 10> velocity=< 2, -2>
    position=< 1,  8> velocity=< 1, -1>
    position=< 1,  7> velocity=< 1,  0>
    position=<-3, 11> velocity=< 1, -2>
    position=< 7,  6> velocity=<-1, -1>
    position=<-2,  3> velocity=< 1,  0>
    position=<-4,  3> velocity=< 2,  0>
    position=<10, -3> velocity=<-1,  1>
    position=< 5, 11> velocity=< 1, -2>
    position=< 4,  7> velocity=< 0, -1>
    position=< 8, -2> velocity=< 0,  1>
    position=<15,  0> velocity=<-2,  0>
    position=< 1,  6> velocity=< 1,  0>
    position=< 8,  9> velocity=< 0, -1>
    position=< 3,  3> velocity=<-1,  1>
    position=< 0,  5> velocity=< 0, -1>
    position=<-2,  2> velocity=< 2,  0>
    position=< 5, -2> velocity=< 1,  2>
    position=< 1,  4> velocity=< 2,  1>
    position=<-2,  7> velocity=< 2, -2>
    position=< 3,  6> velocity=<-1, -1>
    position=< 5,  0> velocity=< 1,  0>
    position=<-6,  0> velocity=< 2,  0>
    position=< 5,  9> velocity=< 1, -2>
    position=<14,  7> velocity=<-2,  0>
    position=<-3,  6> velocity=< 2, -1>"#;

    #[test]
    fn test_solve() {
        let (part_1, part_2) = solve(TEST_INPUT).unwrap();
        assert_eq!(
            part_1,
            r#"#...#..###
#...#...#.
#...#...#.
#####...#.
#...#...#.
#...#...#.
#...#...#.
#...#..###"#,
        );
        assert_eq!(part_2, 3);
    }
}

A 2018/rust/day_11/Cargo.toml => 2018/rust/day_11/Cargo.toml +8 -0
@@ 0,0 1,8 @@
[package]
name = "day_11"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

A 2018/rust/day_11/src/main.rs => 2018/rust/day_11/src/main.rs +141 -0
@@ 0,0 1,141 @@
const GRID_SERIAL_NUMBER: i32 = 1309;

fn calculate_power_level(grid_serial_number: i32, x: i32, y: i32) -> i32 {
    let rack_id = x + 10;
    let power_level = rack_id * y;
    let power_level = power_level + grid_serial_number;
    let power_level = power_level * rack_id;
    let power_level = (power_level / 100) % 10;
    power_level - 5
}

type Grid = [[i32; 300]; 300];

fn generate_grid(grid_serial_number: i32) -> Grid {
    let mut grid = [[0; 300]; 300];
    for x in 0..300 {
        for y in 0..300 {
            grid[x as usize][y as usize] = calculate_power_level(grid_serial_number, x + 1, y + 1);
        }
    }
    grid
}

fn compute_sat(grid: &Grid) -> Grid {
    let mut sat = [[0; 300]; 300];
    for x in 0..300 {
        for y in 0..300 {
            sat[x][y] = grid[x][y];
            if x > 0 {
                sat[x][y] += sat[x - 1][y];
            }
            if y > 0 {
                sat[x][y] += sat[x][y - 1];
            }
            if x > 0 && y > 0 {
                sat[x][y] -= sat[x - 1][y - 1];
            }
        }
    }
    sat
}

fn find_max_3x3(grid: &Grid) -> (i32, i32) {
    let sat = compute_sat(grid);
    let mut max_power_level = 0;
    let mut max_x = 0;
    let mut max_y = 0;
    for x in 0..297 {
        for y in 0..297 {
            let mut power_level = sat[x + 2][y + 2];
            if x > 0 {
                power_level -= sat[x - 1][y + 2];
            }
            if y > 0 {
                power_level -= sat[x + 2][y - 1];
            }
            if x > 0 && y > 0 {
                power_level += sat[x - 1][y - 1];
            }
            if power_level > max_power_level {
                max_power_level = power_level;
                max_x = x;
                max_y = y;
            }
        }
    }
    (max_x as i32 + 1, max_y as i32 + 1)
}

fn part_one(grid_serial_number: i32) -> (i32, i32) {
    let grid = generate_grid(grid_serial_number);
    find_max_3x3(&grid)
}

fn find_max_any_size(grid: &Grid) -> (i32, i32, i32) {
    let sat = compute_sat(grid);
    let mut max_power_level = 0;
    let mut max_x = 0;
    let mut max_y = 0;
    let mut max_size = 0;
    for size in 1..=300 {
        for x in 0..(300 - size) {
            for y in 0..(300 - size) {
                let mut power_level = sat[x + size - 1][y + size - 1];
                if x > 0 {
                    power_level -= sat[x - 1][y + size - 1];
                }
                if y > 0 {
                    power_level -= sat[x + size - 1][y - 1];
                }
                if x > 0 && y > 0 {
                    power_level += sat[x - 1][y - 1];
                }
                if power_level > max_power_level {
                    max_power_level = power_level;
                    max_x = x;
                    max_y = y;
                    max_size = size;
                }
            }
        }
    }
    (max_x as i32 + 1, max_y as i32 + 1, max_size as i32)
}

fn part_two(grid_serial_number: i32) -> (i32, i32, i32) {
    let grid = generate_grid(grid_serial_number);
    find_max_any_size(&grid)
}

fn main() {
    let (x, y) = part_one(GRID_SERIAL_NUMBER);
    println!("Part One: {x},{y}");
    let (x, y, size) = part_two(GRID_SERIAL_NUMBER);
    println!("Part Two: {x},{y},{size}");
}


#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_calculate_power_level() {
        assert_eq!(calculate_power_level(57, 122, 79), -5);
        assert_eq!(calculate_power_level(39, 217, 196), 0);
        assert_eq!(calculate_power_level(71, 101, 153), 4);
    }

    #[test]
    fn test_part_one() {
        assert_eq!(part_one(18), (33, 45));
        assert_eq!(part_one(42), (21, 61));
    }

    #[test]
    fn test_part_two() {
        assert_eq!(part_two(18), (90, 269, 16));
        assert_eq!(part_two(42), (232, 251, 12));
    }
}

A 2018/rust/day_12/Cargo.toml => 2018/rust/day_12/Cargo.toml +8 -0
@@ 0,0 1,8 @@
[package]
name = "day_12"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

A 2018/rust/day_12/src/main.rs => 2018/rust/day_12/src/main.rs +125 -0
@@ 0,0 1,125 @@
use std::collections::{BTreeSet, HashMap};

fn parse_input(input: &str) -> (State, Rules) {
    let mut lines = input.lines();
    let pots = lines
        .next()
        .unwrap()
        .split_whitespace()
        .nth(2)
        .unwrap()
        .chars()
        .enumerate()
        .filter(|(_, c)| *c == '#')
        .map(|(i, _)| i as i64)
        .collect::<BTreeSet<_>>();
    lines.next();
    let rules = lines
        .map(|line| {
            let components = line.trim().split(" => ").collect::<Vec<_>>();
            let pattern = components[0].chars().map(|c| c == '#').collect::<Vec<_>>();
            let pattern = [pattern[0], pattern[1], pattern[2], pattern[3], pattern[4]];
            let result = components[1] == "#";
            (pattern, result)
        })
        .collect::<HashMap<_, _>>();
    (State(pots), Rules(rules))
}

#[derive(Debug)]
struct State(BTreeSet<i64>);

impl State {
    fn get_pattern(&self, i: i64) -> [bool; 5] {
        [-2, -1, 0, 1, 2].map(|j| self.0.contains(&(i + j)))
    }

    fn step(&self, rules: &Rules) -> Self {
        let min = self.0.first().unwrap() - 2;
        let max = self.0.last().unwrap() + 2;
        State(
            (min..=max)
                .filter(|&i| rules.get(&self.get_pattern(i)))
                .collect::<BTreeSet<_>>(),
        )
    }

    fn sum(&self) -> i64 {
        self.0.iter().sum()
    }
}

#[derive(Debug)]
struct Rules(HashMap<[bool; 5], bool>);

impl Rules {
    fn get(&self, pattern: &[bool; 5]) -> bool {
        *self.0.get(pattern).unwrap_or(&false)
    }
}

fn part_one(input: &str) -> i64 {
    let (mut state, rules) = parse_input(input);
    for _ in 0..20 {
        state = state.step(&rules);
    }
    state.sum()
}

fn part_two(input: &str) -> i64 {
    let (mut state, rules) = parse_input(input);

    // We wait for the automaton to reach a steady state and extrapolate from there.
    let mut prev_sum = 0;
    let mut prev_diff = 0;
    let mut prev_diff_count = 0;
    for i in 0.. {
        state = state.step(&rules);
        let sum = state.sum();
        let diff = sum - prev_sum;
        if diff == prev_diff {
            prev_diff_count += 1;
        } else {
            prev_diff_count = 0;
        }
        if prev_diff_count == 10 {
            return sum + (50_000_000_000 - i - 1) * diff;
        }
        prev_sum = sum;
        prev_diff = diff;
    }
    unreachable!()
}

fn main() {
    const INPUT: &str = include_str!("../../../inputs/12.txt");
    println!("Part one: {}", part_one(INPUT));
    println!("Part two: {}", part_two(INPUT));
}

#[cfg(test)]
mod tests {
    use super::*;

    const TEST_INPUT: &str = r#"initial state: #..#.#..##......###...###

...## => #
..#.. => #
.#... => #
.#.#. => #
.#.## => #
.##.. => #
.#### => #
#.#.# => #
#.### => #
##.#. => #
##.## => #
###.. => #
###.# => #
####. => #"#;

    #[test]
    fn test_part_one() {
        assert_eq!(part_one(TEST_INPUT), 325);
    }
}

A 2018/rust/day_13/Cargo.toml => 2018/rust/day_13/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "day_13"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"

A 2018/rust/day_13/src/main.rs => 2018/rust/day_13/src/main.rs +321 -0
@@ 0,0 1,321 @@
use std::{
    collections::{BTreeMap, HashMap},
    fmt::Display,
    str::FromStr,
};

use anyhow::anyhow;

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
enum Track {
    Vertical,
    Horizontal,
    Intersection,
    CurveRight,
    CurveLeft,
}

impl TryFrom<char> for Track {
    type Error = anyhow::Error;

    fn try_from(c: char) -> Result<Self, Self::Error> {
        use Track::*;
        match c {
            '|' => Ok(Vertical),
            '-' => Ok(Horizontal),
            '+' => Ok(Intersection),
            '/' => Ok(CurveRight),
            '\\' => Ok(CurveLeft),
            _ => Err(anyhow!("Invalid track character: {c}")),
        }
    }
}

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

impl TryFrom<char> for Direction {
    type Error = anyhow::Error;

    fn try_from(c: char) -> Result<Self, Self::Error> {
        use Direction::*;
        match c {
            '^' => Ok(Up),
            'v' => Ok(Down),
            '<' => Ok(Left),
            '>' => Ok(Right),
            _ => Err(anyhow!("Invalid direction character: {c}")),
        }
    }
}

#[derive(PartialEq, Eq, Debug)]
enum Turn {
    Left,
    Straight,
    Right,
}

impl Turn {
    fn next(&self) -> Turn {
        use Turn::*;
        match self {
            Left => Straight,
            Straight => Right,
            Right => Left,
        }
    }
}

struct Cart {
    position: Position,
    direction: Direction,
    next_turn: Turn,
}

impl Cart {
    fn new(position: Position, direction: Direction) -> Cart {
        Cart {
            position,
            direction,
            next_turn: Turn::Left,
        }
    }

    fn step(&mut self, map: &Map) {
        self.position = match self.direction {
            Direction::Up => Position {
                y: self.position.y - 1,
                x: self.position.x,
            },
            Direction::Down => Position {
                y: self.position.y + 1,
                x: self.position.x,
            },
            Direction::Left => Position {
                y: self.position.y,
                x: self.position.x - 1,
            },
            Direction::Right => Position {
                y: self.position.y,
                x: self.position.x + 1,
            },
        };
        let new_track = map.get(&self.position).unwrap();
        match new_track {
            Track::Vertical | Track::Horizontal => (),
            Track::CurveRight => {
                self.direction = match self.direction {
                    Direction::Up => Direction::Right,
                    Direction::Down => Direction::Left,
                    Direction::Left => Direction::Down,
                    Direction::Right => Direction::Up,
                }
            }
            Track::CurveLeft => {
                self.direction = match self.direction {
                    Direction::Up => Direction::Left,
                    Direction::Down => Direction::Right,
                    Direction::Left => Direction::Up,
                    Direction::Right => Direction::Down,
                }
            }
            Track::Intersection => {
                match self.next_turn {
                    Turn::Left => {
                        self.direction = match self.direction {
                            Direction::Up => Direction::Left,
                            Direction::Down => Direction::Right,
                            Direction::Left => Direction::Down,
                            Direction::Right => Direction::Up,
                        }
                    }
                    Turn::Straight => {}
                    Turn::Right => {
                        self.direction = match self.direction {
                            Direction::Up => Direction::Right,
                            Direction::Down => Direction::Left,
                            Direction::Left => Direction::Up,
                            Direction::Right => Direction::Down,
                        }
                    }
                }
                self.next_turn = self.next_turn.next();
            }
        }
    }
}

#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy, Hash)]
struct Position {
    y: usize,
    x: usize,
}

type Map = HashMap<Position, Track>;

struct State {
    tracks: Map,
    carts: BTreeMap<Position, Cart>,
}

enum StepResult {
    OnlyCart(Position),
    Collision(Position),
    Nothing,
}

impl State {
    fn step(&mut self) -> StepResult {
        let mut first_collision = None;
        for position in self.carts.keys().copied().collect::<Vec<_>>() {
            let Some(mut cart) = self.carts.remove(&position) else { continue; };
            cart.step(&self.tracks);
            match self.carts.entry(cart.position) {
                std::collections::btree_map::Entry::Vacant(e) => {
                    e.insert(cart);
                }
                std::collections::btree_map::Entry::Occupied(_) => {
                    self.carts.remove(&cart.position);
                    if first_collision.is_none() {
                        first_collision = Some(cart.position);
                    }
                }
            }
        }
        if self.carts.len() == 1 {
            StepResult::OnlyCart(*self.carts.first_key_value().unwrap().0)
        } else if let Some(collision) = first_collision {
            StepResult::Collision(collision)
        } else {
            StepResult::Nothing
        }
    }
}

impl FromStr for State {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut tracks = Map::new();
        let mut carts = BTreeMap::new();
        for (y, line) in s.lines().enumerate() {
            for (x, c) in line.chars().enumerate() {
                let position = Position { x, y };
                match c {
                    ' ' => (),
                    _ => {
                        if let Ok(track) = Track::try_from(c) {
                            tracks.insert(position, track);
                        } else if let Ok(direction) = Direction::try_from(c) {
                            carts.insert(position, Cart::new(position, direction));
                            // There is a track under the cart
                            let track_under_cart = match direction {
                                Direction::Up | Direction::Down => Track::Vertical,
                                Direction::Left | Direction::Right => Track::Horizontal,
                            };
                            tracks.insert(position, track_under_cart);
                        } else {
                            return Err(anyhow!("Invalid character: {}", c));
                        }
                    }
                }
            }
        }
        Ok(State { tracks, carts })
    }
}

impl Display for State {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let max_x = self.tracks.keys().map(|pos| pos.x).max().unwrap();
        let max_y = self.tracks.keys().map(|pos| pos.y).max().unwrap();

        for y in 0..=max_y {
            for x in 0..=max_x {
                let position = Position { x, y };
                if let Some(cart) = self.carts.get(&position) {
                    let c = match cart.direction {
                        Direction::Up => '^',
                        Direction::Down => 'v',
                        Direction::Left => '<',
                        Direction::Right => '>',
                    };
                    write!(f, "{}", c)?;
                } else if let Some(track) = self.tracks.get(&position) {
                    let c = match track {
                        Track::Vertical => '|',
                        Track::Horizontal => '-',
                        Track::Intersection => '+',
                        Track::CurveRight => '/',
                        Track::CurveLeft => '\\',
                    };
                    write!(f, "{}", c)?;
                } else {
                    write!(f, " ")?;
                }
            }
            writeln!(f)?;
        }
        Ok(())
    }
}

fn part_one(input: &str) -> (usize, usize) {
    let mut state: State = input.parse().unwrap();
    loop {
        if let StepResult::Collision(Position { x, y }) = state.step() {
            return (x, y);
        }
    }
}

fn part_two(input: &str) -> (usize, usize) {
    let mut state: State = input.parse().unwrap();
    loop {
        if let StepResult::OnlyCart(Position { x, y }) = state.step() {
            return (x, y);
        }
    }
}

fn main() {
    const INPUT: &str = include_str!("../../../inputs/13.txt");
    println!("Part one: {:?}", part_one(INPUT));
    println!("Part two: {:?}", part_two(INPUT));
}

#[cfg(test)]
mod tests {
    use super::*;

    const PART_ONE_TEST_INPUT: &str = r#"/->-\        
|   |  /----\
| /-+--+-\  |
| | |  | v  |
\-+-/  \-+--/
  \------/   "#;

    #[test]
    fn test_part_one() {
        assert_eq!(part_one(PART_ONE_TEST_INPUT), (7, 3));
    }

    const PART_TWO_TEST_INPUT: &str = r#"/>-<\  
|   |  
| /<+-\
| | | v
\>+</ |
  |   ^
  \<->/"#;

    #[test]
    fn test_part_two() {
        assert_eq!(part_two(PART_TWO_TEST_INPUT), (6, 4));
    }
}

A 2018/rust/run.py => 2018/rust/run.py +51 -0
@@ 0,0 1,51 @@
#! /usr/bin/env python3

import itertools
import json
import re
import subprocess
import sys


def benchmark_target(target: str) -> float:
    # Build the target
    subprocess.run(["cargo", "build", "--release", "--bin", target], check=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    # Run the target 5 times and discard the fastest and slowest times.
    num_samples = 5
    times = []
    for _ in range(num_samples):
        bash_command = f"time ./target/release/{target}"
        output = subprocess.run(["bash", "-c", bash_command], check=True, capture_output=True, text=True)
        match = re.search(r"real\s+(\d)+m(\d+\.\d+)s", output.stderr)
        assert match is not None, f"Failed to parse time output: {output.stderr}"
        minutes = int(match.group(1))
        assert minutes == 0, f"Target took more than 1 minute to run"
        seconds = float(match.group(2))
        times.append(seconds)

    return sum(sorted(times)[1:-1]) / (num_samples - 2)


def get_all_targets() -> list[str]:
    result = subprocess.run(["cargo", "metadata"], check=True, capture_output=True, text=True)
    metadata = json.loads(result.stdout)
    all_targets = itertools.chain.from_iterable(package["targets"] for package in metadata["packages"])
    return [target["name"] for target in all_targets if target["kind"] == ["bin"]]


def main() -> int:
    total_duration = 0.0
    subprocess.run(["cargo", "build", "--release"], check=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    for target in get_all_targets():
        print(f"Benchmarking {target}... ", end="", flush=True)
        seconds = benchmark_target(target)
        print(f"{seconds:.3f} seconds")
        total_duration += seconds
    print(f"Total duration: {total_duration:.3f} seconds")

    return 0


if __name__ == "__main__":
    sys.exit(main())