From 35ba924c1b1b5a8b31c8129b449b08b1993460f2 Mon Sep 17 00:00:00 2001
From: Joe Ardent <code@ardent.nebcorp.com>
Date: Mon, 23 Dec 2024 13:00:41 -0800
Subject: [PATCH] day5, part2

---
 Cargo.lock        |   7 ++
 Cargo.toml        |   2 +-
 day05/Cargo.toml  |   7 ++
 day05/src/main.rs | 179 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 194 insertions(+), 1 deletion(-)
 create mode 100644 day05/Cargo.toml
 create mode 100644 day05/src/main.rs

diff --git a/Cargo.lock b/Cargo.lock
index 9f85b3c..0b394f7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -24,6 +24,13 @@ dependencies = [
 name = "day04"
 version = "0.1.0"
 
+[[package]]
+name = "day05"
+version = "0.1.0"
+dependencies = [
+ "winnow",
+]
+
 [[package]]
 name = "memchr"
 version = "2.7.4"
diff --git a/Cargo.toml b/Cargo.toml
index 95300e0..766f8ff 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [workspace]
 resolver = "2"
-members = ["day01", "day02", "day03", "day04"]
+members = ["day01", "day02", "day03", "day04", "day05"]
 
 [workspace.dependencies]
 winnow = "0.6"
diff --git a/day05/Cargo.toml b/day05/Cargo.toml
new file mode 100644
index 0000000..19e83f5
--- /dev/null
+++ b/day05/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "day05"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+winnow.workspace = true
diff --git a/day05/src/main.rs b/day05/src/main.rs
new file mode 100644
index 0000000..3636f2b
--- /dev/null
+++ b/day05/src/main.rs
@@ -0,0 +1,179 @@
+use std::collections::{HashMap, HashSet};
+
+use winnow::{
+    PResult, Parser,
+    ascii::{dec_uint, newline},
+    combinator::{separated, separated_pair},
+    error::ContextError,
+};
+
+fn main() {
+    let input = std::fs::read_to_string("input").unwrap();
+    println!("{}", pt1(&input));
+    println!("{}", pt2(&input));
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+struct Rule(u32, u32);
+
+type Rules = Vec<Rule>;
+type Updates = Vec<Vec<u32>>;
+type Graph = HashMap<u32, HashSet<u32>>;
+
+fn pt1(input: &str) -> u32 {
+    let mut input = input;
+    let (rules, updates) = parse(&mut input).unwrap();
+    let mut total = 0;
+    for update in updates {
+        if check(&rules, &update) {
+            let mid = update.len() / 2;
+            total += update[mid];
+        }
+    }
+    total
+}
+
+fn check(rules: &Rules, update: &[u32]) -> bool {
+    let mut pages = HashMap::new();
+    for (i, p) in update.iter().enumerate() {
+        pages.insert(p, i);
+    }
+    for rule in rules {
+        let Rule(before, after) = rule;
+        if let Some(u) = pages.get(before) {
+            if let Some(v) = pages.get(after) {
+                if v <= u {
+                    return false;
+                }
+            }
+        }
+    }
+    true
+}
+
+fn pt2(input: &str) -> u32 {
+    let mut input = input;
+    let mut total = 0;
+
+    let (rules, updates) = parse(&mut input).unwrap();
+
+    let mut neighbors = Graph::new();
+    for rule in &rules {
+        let Rule(u, v) = rule;
+        neighbors.entry(*u).or_default().insert(*v);
+    }
+
+    for update in updates {
+        if !check(&rules, &update) {
+            let sorted = tsort(&neighbors, &update);
+            let mid = sorted.len() / 2;
+            total += sorted[mid];
+        }
+    }
+
+    total
+}
+
+fn tsort(graph: &Graph, nodes: &[u32]) -> Vec<u32> {
+    let mut sorted = Vec::with_capacity(nodes.len());
+    let mut processed = HashSet::with_capacity(nodes.len());
+    let mut q = Vec::new();
+    let nodes: HashSet<u32> = nodes.iter().copied().collect();
+
+    for node in nodes.iter() {
+        q.push(*node);
+        while let Some(current) = q.pop() {
+            if !processed.contains(&current) {
+                q.push(current);
+            }
+
+            let mut do_top = true;
+            if let Some(nexts) = graph.get(&current) {
+                for n in nexts.iter().filter(|v| nodes.contains(v) && **v != current) {
+                    if !processed.contains(n) {
+                        q.push(*n);
+                        do_top = false;
+                    }
+                }
+            }
+
+            if do_top {
+                let _ = q.pop();
+                if processed.insert(current) {
+                    sorted.push(current);
+                }
+            }
+        }
+    }
+
+    sorted
+}
+
+fn p_rule(input: &mut &str) -> PResult<Rule> {
+    let (a, b) = separated_pair(dec_uint, '|', dec_uint).parse_next(input)?;
+    Ok(Rule(a, b))
+}
+
+fn p_update(input: &mut &str) -> PResult<Vec<u32>> {
+    separated(1.., dec_uint::<&str, u32, ContextError>, ',').parse_next(input)
+}
+
+fn p_input(input: &mut &str) -> PResult<(Vec<Rule>, Updates)> {
+    separated_pair(
+        separated(1.., p_rule, newline),
+        (newline, newline),
+        separated(1.., p_update, newline),
+    )
+    .parse_next(input)
+}
+
+fn parse(input: &mut &str) -> PResult<(Rules, Updates)> {
+    let (rules, updates) = p_input(input)?;
+
+    Ok((rules, updates))
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    static INPUT: &str = "47|53
+97|13
+97|61
+97|47
+75|29
+61|13
+75|53
+29|13
+97|29
+53|29
+61|53
+97|53
+61|29
+47|13
+75|47
+97|75
+47|61
+75|61
+47|29
+75|13
+53|13
+
+75,47,61,53,29
+97,61,53,29,13
+75,29,13
+75,97,47,61,53
+61,13,29
+97,13,75,29,47";
+
+    #[test]
+    fn p1() {
+        let v = pt1(INPUT);
+        assert_eq!(v, 143)
+    }
+
+    #[test]
+    fn p2() {
+        assert_eq!(123, pt2(INPUT));
+    }
+}