diff --git a/day12/Cargo.lock b/day12/Cargo.lock index d6b3c89..90c52b2 100644 --- a/day12/Cargo.lock +++ b/day12/Cargo.lock @@ -2,20 +2,10 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - [[package]] name = "day12" version = "0.1.0" dependencies = [ - "regex", "serde", "serde_json", ] @@ -50,35 +40,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - [[package]] name = "serde" version = "1.0.228" diff --git a/day12/Cargo.toml b/day12/Cargo.toml index a09fc61..d32d887 100644 --- a/day12/Cargo.toml +++ b/day12/Cargo.toml @@ -4,6 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] -regex = "1.12.4" serde = "1.0.228" serde_json = "1.0.150" diff --git a/day12/src/main.rs b/day12/src/main.rs index 08c1ab5..0b61148 100644 --- a/day12/src/main.rs +++ b/day12/src/main.rs @@ -1,243 +1,69 @@ -//! Sums all numbers in a JSON document, with an optional rule that skips -//! any object containing the string value "red" (and everything nested -//! inside it). Arrays are unaffected by "red". -//! -//! Usage: -//! sum_json [path] reads the given file, or stdin if no path given -//! -//! No external crates -- just a small recursive-descent JSON parser over -//! `std`. +use serde_json::Value::{self, String}; -use std::env; -use std::fs; -use std::io::{self, Read}; -use std::iter::Peekable; -use std::str::Chars; +fn main() { + let content = std::fs::read_to_string("input.json").unwrap(); + let json: Value = serde_json::from_str(&content).unwrap(); -/// A parsed JSON value. Only the variants this puzzle cares about: numbers -/// are kept as `i64` since the inputs are always integers. -#[derive(Debug, Clone, PartialEq)] -enum Json { - Number(i64), - String(String), - Array(Vec), - Object(Vec<(String, Json)>), + let result: i64 = walk(&json).iter().sum(); + println!("{result}"); + + let result: i64 = walk_ignore_red(&json).iter().sum(); + println!("{result}"); } -struct Parser<'a> { - chars: Peekable>, -} - -impl Json { - fn parse(input: &str) -> Json { - let mut parser = Parser::new(input); - parser.skip_whitespace(); - parser.parse_value() - } -} - -impl<'a> Parser<'a> { - fn new(input: &'a str) -> Self { - Parser { - chars: input.chars().peekable(), - } - } - - fn skip_whitespace(&mut self) { - while self.chars.peek().is_some_and(|c| c.is_whitespace()) { - self.chars.next(); - } - } - - fn parse_value(&mut self) -> Json { - self.skip_whitespace(); - match self.chars.peek() { - Some('{') => self.parse_object(), - Some('[') => self.parse_array(), - Some('"') => Json::String(self.parse_string()), - Some(c) if c.is_ascii_digit() || *c == '-' => self.parse_number(), - other => panic!("unexpected character while parsing value: {other:?}"), - } - } - - fn parse_object(&mut self) -> Json { - self.chars.next(); // consume '{' - let mut entries = Vec::new(); - - self.skip_whitespace(); - if self.chars.peek() == Some(&'}') { - self.chars.next(); - return Json::Object(entries); - } - - loop { - self.skip_whitespace(); - let key = self.parse_string(); - self.skip_whitespace(); - self.expect(':'); - let value = self.parse_value(); - entries.push((key, value)); - - self.skip_whitespace(); - match self.chars.next() { - Some(',') => continue, - Some('}') => break, - other => panic!("expected ',' or '}}' in object, got {other:?}"), - } - } - - Json::Object(entries) - } - - fn parse_array(&mut self) -> Json { - self.chars.next(); // consume '[' - let mut items = Vec::new(); - - self.skip_whitespace(); - if self.chars.peek() == Some(&']') { - self.chars.next(); - return Json::Array(items); - } - - loop { - items.push(self.parse_value()); - self.skip_whitespace(); - match self.chars.next() { - Some(',') => continue, - Some(']') => break, - other => panic!("expected ',' or ']' in array, got {other:?}"), - } - } - - Json::Array(items) - } - - fn parse_string(&mut self) -> String { - self.expect('"'); - let mut s = String::new(); - loop { - match self.chars.next() { - Some('"') => break, - Some('\\') => match self.chars.next() { - Some('n') => s.push('\n'), - Some('t') => s.push('\t'), - Some('r') => s.push('\r'), - Some(other) => s.push(other), // covers \" \\ \/ etc. - None => panic!("unterminated escape sequence"), - }, - Some(c) => s.push(c), - None => panic!("unterminated string"), - } - } - s - } - - fn parse_number(&mut self) -> Json { - let mut s = String::new(); - if self.chars.peek() == Some(&'-') { - s.push(self.chars.next().unwrap()); - } - while let Some(&c) = self.chars.peek() { - if c.is_ascii_digit() { - s.push(c); - self.chars.next(); - } else { - break; - } - } - Json::Number(s.parse().expect("malformed number")) - } - - fn expect(&mut self, expected: char) { - match self.chars.next() { - Some(c) if c == expected => {} - other => panic!("expected '{expected}', got {other:?}"), - } - } -} - -/// Part 1: sum every number in the document, no exceptions. -fn sum_all(value: &Json) -> i64 { +fn walk(value: &Value) -> Vec { match value { - Json::Number(n) => *n, - Json::String(_) => 0, - Json::Array(items) => items.iter().map(sum_all).sum(), - Json::Object(entries) => entries.iter().map(|(_, v)| sum_all(v)).sum(), - } -} - -/// Part 2: same as `sum_all`, but any object with a property whose value -/// is the string "red" contributes 0, including everything nested inside -/// it. Arrays are never skipped, only objects. -fn sum_excluding_red(value: &Json) -> i64 { - match value { - Json::Number(n) => *n, - Json::String(_) => 0, - Json::Array(items) => items.iter().map(sum_excluding_red).sum(), - Json::Object(entries) => { - let is_red = entries - .iter() - .any(|(_, v)| matches!(v, Json::String(s) if s == "red")); - if is_red { - 0 - } else { - entries.iter().map(|(_, v)| sum_excluding_red(v)).sum() + Value::Object(map) => { + let mut result = vec![]; + for (_key, value) in map { + walk(value).iter().for_each(|val| result.push(*val)); } + result } - } -} - -fn read_input() -> io::Result { - match env::args().nth(1) { - Some(path) => fs::read_to_string(path), - None => { - let mut buf = String::new(); - io::stdin().read_to_string(&mut buf)?; - Ok(buf) + Value::Array(items) => { + let mut result = vec![]; + for value in items { + walk(value).iter().for_each(|val| result.push(*val)); + } + result } + Value::Number(num) => { + vec![num.as_i64().unwrap()] + } + _leaf => vec![], } } -fn main() -> io::Result<()> { - let input = read_input()?; - let document = Json::parse(&input); +fn walk_ignore_red(value: &Value) -> Vec { + match value { + Value::Object(map) => { + let mut result = vec![]; - println!("Sum of all numbers: {}", sum_all(&document)); - println!( - "Sum excluding red objects: {}", - sum_excluding_red(&document) - ); + for val in map.values() { + if let String(s) = val + && s == "red" + { + return result; + } + } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sum_all_examples() { - assert_eq!(sum_all(&Json::parse(r#"[1,2,3]"#)), 6); - assert_eq!(sum_all(&Json::parse(r#"{"a":2,"b":4}"#)), 6); - assert_eq!(sum_all(&Json::parse(r#"[[[3]]]"#)), 3); - assert_eq!(sum_all(&Json::parse(r#"{"a":{"b":4},"c":-1}"#)), 3); - assert_eq!(sum_all(&Json::parse(r#"{"a":[-1,1]}"#)), 0); - assert_eq!(sum_all(&Json::parse(r#"[-1,{"a":1}]"#)), 0); - assert_eq!(sum_all(&Json::parse(r#"[]"#)), 0); - assert_eq!(sum_all(&Json::parse(r#"{}"#)), 0); - } - - #[test] - fn sum_excluding_red_examples() { - assert_eq!(sum_excluding_red(&Json::parse(r#"[1,2,3]"#)), 6); - assert_eq!( - sum_excluding_red(&Json::parse(r#"[1,{"c":"red","b":2},3]"#)), - 4 - ); - assert_eq!( - sum_excluding_red(&Json::parse(r#"{"d":"red","e":[1,2,3,4],"f":5}"#)), - 0 - ); - assert_eq!(sum_excluding_red(&Json::parse(r#"[1,"red",5]"#)), 6); + for value in map.values() { + walk_ignore_red(value) + .iter() + .for_each(|val| result.push(*val)); + } + result + } + Value::Array(items) => { + let mut result = vec![]; + for value in items { + walk_ignore_red(value) + .iter() + .for_each(|val| result.push(*val)); + } + result + } + Value::Number(num) => vec![num.as_i64().unwrap()], + _leaf => vec![], } }