From bccc0587fd6ab422801e28ce0592017c0340ab92 Mon Sep 17 00:00:00 2001 From: timeshifter Date: Mon, 22 Jun 2026 21:33:20 +0200 Subject: [PATCH] add day 12 part 2 solved with AI -- claude.ai --- day12/src/main.rs | 253 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 236 insertions(+), 17 deletions(-) diff --git a/day12/src/main.rs b/day12/src/main.rs index 5b1f96a..08c1ab5 100644 --- a/day12/src/main.rs +++ b/day12/src/main.rs @@ -1,24 +1,243 @@ -use regex::Regex; -use serde_json::Value; +//! 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`. -fn main() { - let content = std::fs::read_to_string("input.json").unwrap(); +use std::env; +use std::fs; +use std::io::{self, Read}; +use std::iter::Peekable; +use std::str::Chars; - part1(&content); - part2(&content); +/// 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)>), } -fn part2(content: &str) { - let json: Value = serde_json::from_str(content).unwrap(); - - // dbg!(json.as_object().unwrap().iter().next().unwrap()); +struct Parser<'a> { + chars: Peekable>, } -fn part1(content: &str) { - let re = Regex::new("-?[0-9]+").unwrap(); - let count: isize = re - .find_iter(content) - .map(|s| s.as_str().parse::().unwrap()) - .sum(); - println!("{count}"); +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 { + 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() + } + } + } +} + +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) + } + } +} + +fn main() -> io::Result<()> { + let input = read_input()?; + let document = Json::parse(&input); + + println!("Sum of all numbers: {}", sum_all(&document)); + println!( + "Sum excluding red objects: {}", + sum_excluding_red(&document) + ); + + 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); + } }