Skip to main content

advent_of_code/year2015/
day12.rs

1use crate::input::Input;
2use std::collections::HashMap;
3
4#[allow(clippy::use_self)]
5#[derive(Eq, PartialEq, Debug)]
6enum JsonValue<'a> {
7    String(&'a [u8]),
8    Number(i32),
9    Array(Vec<JsonValue<'a>>),
10    Object(HashMap<&'a [u8], JsonValue<'a>>),
11    Comma,
12    Colon,
13    EndOfArray,
14    EndOfObject,
15    EndOfInput,
16}
17
18fn parse<'a>(input: &'a [u8], current_idx: &mut usize) -> Result<JsonValue<'a>, String> {
19    if *current_idx == input.len() {
20        return Ok(JsonValue::EndOfInput);
21    }
22    let next_char = input[*current_idx];
23    *current_idx += 1;
24
25    Ok(match next_char {
26        b'{' => {
27            let mut object_map = HashMap::new();
28            loop {
29                let mut next_key = parse(input, current_idx)?;
30                if next_key == JsonValue::Comma {
31                    next_key = parse(input, current_idx)?;
32                }
33
34                if JsonValue::EndOfObject == next_key {
35                    break JsonValue::Object(object_map);
36                } else if let JsonValue::String(key) = next_key {
37                    let next_colon = parse(input, current_idx)?;
38                    if next_colon != JsonValue::Colon {
39                        return Err("Invalid JSON - key not followed by colon".to_string());
40                    }
41
42                    let next_value = parse(input, current_idx)?;
43                    object_map.insert(key, next_value);
44                } else {
45                    return Err(format!(
46                        "Not key or colon in object: {:?} (index={})",
47                        next_key, *current_idx
48                    ));
49                }
50            }
51        }
52        b'}' => JsonValue::EndOfObject,
53        b':' => JsonValue::Colon,
54        b'[' => {
55            let mut array = Vec::new();
56            loop {
57                let next_value = parse(input, current_idx)?;
58                if JsonValue::EndOfArray == next_value {
59                    break JsonValue::Array(array);
60                } else if JsonValue::EndOfInput == next_value {
61                    return Err("Invalid JSON".to_string());
62                } else if JsonValue::Comma == next_value {
63                    // Ignore
64                } else {
65                    array.push(next_value);
66                }
67            }
68        }
69        b']' => JsonValue::EndOfArray,
70        b',' => JsonValue::Comma,
71        b'"' => {
72            for (idx, &read_char) in input.iter().enumerate().skip(*current_idx) {
73                if read_char == b'"' {
74                    let start_idx = *current_idx;
75                    *current_idx = idx + 1;
76                    return Ok(JsonValue::String(&input[start_idx..idx]));
77                }
78            }
79            return Err("Invalid input - no end of string".to_string());
80        }
81        b'0'..=b'9' | b'-' => {
82            let mut idx = *current_idx;
83            let (next_char, sign) = if next_char == b'-' {
84                let res = (input[idx], -1);
85                idx += 1;
86                res
87            } else {
88                (next_char, 1)
89            };
90            let mut value = sign * i32::from(next_char - b'0');
91
92            loop {
93                let read_char = if idx == input.len() { b' ' } else { input[idx] };
94                if read_char.is_ascii_digit() {
95                    value = value
96                        .checked_mul(10_i32)
97                        .and_then(|v| v.checked_add(sign * i32::from(read_char - b'0')))
98                        .ok_or("Non-i32 number")?;
99                } else {
100                    *current_idx = idx;
101                    break JsonValue::Number(value);
102                }
103                idx += 1;
104            }
105        }
106        _ => {
107            return Err(format!(
108                "Invalid char: '{}' at index={}",
109                next_char as char, *current_idx
110            ));
111        }
112    })
113}
114
115fn sum_json_value(value: &JsonValue, part2: bool) -> i32 {
116    match value {
117        JsonValue::Number(n) => *n,
118        JsonValue::Array(vec) => vec.iter().map(|value| sum_json_value(value, part2)).sum(),
119        JsonValue::Object(map) => {
120            if part2
121                && map
122                    .values()
123                    .any(|value| value == &JsonValue::String(b"red"))
124            {
125                0
126            } else {
127                map.values().map(|value| sum_json_value(value, part2)).sum()
128            }
129        }
130        _ => 0,
131    }
132}
133
134pub fn solve(input: &Input) -> Result<i32, String> {
135    let mut current_idx = 0_usize;
136    let json_value = parse(input.text.as_bytes(), &mut current_idx)?;
137    let sum = sum_json_value(&json_value, input.is_part_two());
138    Ok(sum)
139}
140
141#[test]
142pub fn test_parse() {
143    let mut current_idx = 0_usize;
144    assert_eq!(
145        Ok(JsonValue::Number(1234)),
146        parse(b"1234", &mut current_idx)
147    );
148
149    current_idx = 0;
150    assert_eq!(
151        Ok(JsonValue::String(b"1234")),
152        parse(b"\"1234\"", &mut current_idx)
153    );
154
155    current_idx = 0;
156    assert_eq!(
157        Ok(JsonValue::Number(i32::MAX)),
158        parse(b"2147483647", &mut current_idx)
159    );
160
161    current_idx = 0;
162    assert_eq!(
163        Ok(JsonValue::Number(i32::MIN)),
164        parse(b"-2147483648", &mut current_idx)
165    );
166
167    for input in [
168        b"2147483648".as_slice(),
169        b"-2147483649".as_slice(),
170        b"9000000000".as_slice(),
171        b"-9000000000".as_slice(),
172    ] {
173        current_idx = 0;
174        assert_eq!(
175            Err("Non-i32 number".to_string()),
176            parse(input, &mut current_idx)
177        );
178    }
179
180    current_idx = 0;
181    assert_eq!(
182        Ok(JsonValue::Array(vec![
183            JsonValue::Number(123),
184            JsonValue::String(b"abc")
185        ])),
186        parse(b"[123,\"abc\"]", &mut current_idx)
187    );
188
189    current_idx = 0;
190    let mut expected_map = HashMap::new();
191    let key1 = b"key1";
192    let key2 = b"key2";
193    let key3 = b"key3";
194    expected_map.insert(&key1[..], JsonValue::Number(123));
195    expected_map.insert(&key2[..], JsonValue::String(b"abc"));
196    expected_map.insert(
197        &key3[..],
198        JsonValue::Array(vec![JsonValue::Number(-345), JsonValue::String(b"abc")]),
199    );
200    assert_eq!(
201        Ok(JsonValue::Object(expected_map)),
202        parse(
203            b"{\"key1\":123,\"key2\":\"abc\",\"key3\":[-345,\"abc\"]}",
204            &mut current_idx
205        )
206    );
207}
208
209#[test]
210pub fn tests() {
211    test_part_one!("{\"a\":{\"b\":4},\"c\":-1}" => 3);
212    test_part_one!("[1,2,3]" => 6);
213    test_part_one!("{\"a\":2,\"b\":4}" => 6);
214    test_part_one!("[[[3]]]" => 3);
215    test_part_one!("{\"a\":[-1,1]}" => 0);
216    test_part_one!("[-1,{\"a\":1}]" => 0);
217    test_part_one!("[]" => 0);
218    test_part_one!("{}" => 0);
219
220    test_part_two!("[1,{\"c\":\"red\",\"b\":2},3]" => 4);
221
222    let real_input = include_str!("day12_input.txt");
223    test_part_one!(real_input => 111_754);
224    test_part_two!(real_input => 65_402);
225}