reimplement 12 with serde
This commit is contained in:
Generated
-39
@@ -2,20 +2,10 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
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]]
|
[[package]]
|
||||||
name = "day12"
|
name = "day12"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
@@ -50,35 +40,6 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"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]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
|
|||||||
@@ -4,6 +4,5 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "1.12.4"
|
|
||||||
serde = "1.0.228"
|
serde = "1.0.228"
|
||||||
serde_json = "1.0.150"
|
serde_json = "1.0.150"
|
||||||
|
|||||||
+53
-227
@@ -1,243 +1,69 @@
|
|||||||
//! Sums all numbers in a JSON document, with an optional rule that skips
|
use serde_json::Value::{self, String};
|
||||||
//! 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 std::env;
|
fn main() {
|
||||||
use std::fs;
|
let content = std::fs::read_to_string("input.json").unwrap();
|
||||||
use std::io::{self, Read};
|
let json: Value = serde_json::from_str(&content).unwrap();
|
||||||
use std::iter::Peekable;
|
|
||||||
use std::str::Chars;
|
|
||||||
|
|
||||||
/// A parsed JSON value. Only the variants this puzzle cares about: numbers
|
let result: i64 = walk(&json).iter().sum();
|
||||||
/// are kept as `i64` since the inputs are always integers.
|
println!("{result}");
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
enum Json {
|
let result: i64 = walk_ignore_red(&json).iter().sum();
|
||||||
Number(i64),
|
println!("{result}");
|
||||||
String(String),
|
|
||||||
Array(Vec<Json>),
|
|
||||||
Object(Vec<(String, Json)>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Parser<'a> {
|
fn walk(value: &Value) -> Vec<i64> {
|
||||||
chars: Peekable<Chars<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
match value {
|
||||||
Json::Number(n) => *n,
|
Value::Object(map) => {
|
||||||
Json::String(_) => 0,
|
let mut result = vec![];
|
||||||
Json::Array(items) => items.iter().map(sum_all).sum(),
|
for (_key, value) in map {
|
||||||
Json::Object(entries) => entries.iter().map(|(_, v)| sum_all(v)).sum(),
|
walk(value).iter().for_each(|val| result.push(*val));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
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![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Part 2: same as `sum_all`, but any object with a property whose value
|
fn walk_ignore_red(value: &Value) -> Vec<i64> {
|
||||||
/// 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 {
|
match value {
|
||||||
Json::Number(n) => *n,
|
Value::Object(map) => {
|
||||||
Json::String(_) => 0,
|
let mut result = vec![];
|
||||||
Json::Array(items) => items.iter().map(sum_excluding_red).sum(),
|
|
||||||
Json::Object(entries) => {
|
for val in map.values() {
|
||||||
let is_red = entries
|
if let String(s) = val
|
||||||
|
&& s == "red"
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for value in map.values() {
|
||||||
|
walk_ignore_red(value)
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(_, v)| matches!(v, Json::String(s) if s == "red"));
|
.for_each(|val| result.push(*val));
|
||||||
if is_red {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
entries.iter().map(|(_, v)| sum_excluding_red(v)).sum()
|
|
||||||
}
|
}
|
||||||
|
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()],
|
||||||
fn read_input() -> io::Result<String> {
|
_leaf => vec![],
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user