add day 12 part 2

solved with AI -- claude.ai
This commit is contained in:
timeshifter
2026-06-22 21:33:20 +02:00
parent cb0796b44b
commit bccc0587fd
+236 -17
View File
@@ -1,24 +1,243 @@
use regex::Regex; //! Sums all numbers in a JSON document, with an optional rule that skips
use serde_json::Value; //! 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() { use std::env;
let content = std::fs::read_to_string("input.json").unwrap(); use std::fs;
use std::io::{self, Read};
use std::iter::Peekable;
use std::str::Chars;
part1(&content); /// A parsed JSON value. Only the variants this puzzle cares about: numbers
part2(&content); /// are kept as `i64` since the inputs are always integers.
#[derive(Debug, Clone, PartialEq)]
enum Json {
Number(i64),
String(String),
Array(Vec<Json>),
Object(Vec<(String, Json)>),
} }
fn part2(content: &str) { struct Parser<'a> {
let json: Value = serde_json::from_str(content).unwrap(); chars: Peekable<Chars<'a>>,
// dbg!(json.as_object().unwrap().iter().next().unwrap());
} }
fn part1(content: &str) { impl Json {
let re = Regex::new("-?[0-9]+").unwrap(); fn parse(input: &str) -> Json {
let count: isize = re let mut parser = Parser::new(input);
.find_iter(content) parser.skip_whitespace();
.map(|s| s.as_str().parse::<isize>().unwrap()) parser.parse_value()
.sum(); }
println!("{count}"); }
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<String> {
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);
}
} }