Advent of Code 2020 day04

5 min read読了の目安(約4800字

https://adventofcode.com/2020/day/4

part1

ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
byr:1937 iyr:2017 cid:147 hgt:183cm

iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
hcl:#cfa07d byr:1929

hcl:#ae17e1 iyr:2013
eyr:2024
ecl:brn pid:760753108 byr:1931
hgt:179cm

hcl:#cfa07d eyr:2025 pid:166559648
iyr:2011 ecl:brn hgt:59in

のような入力が与えられる。空行区切りでひとまとめのdataで、各dataはspaceまたは改行で区切られたfieldを持ち、各fieldは:で区切られたkey-valueとなる。

まずは cid だけがoptionalなので欠損していてもよく、それ以外はrequiredとしたときにvalidな件数は幾つか、という問題。
ただその条件の通りにparseしてcountするだけ。

入力を丸ごと読み込んでから \n\nsplitしてやればdata列挙しやすかったかもしれないけど、1行ずつ読み込んだものを扱う方針でやっていたので空行が来たら判定、とした。最後のdataを無視してしまわないように iter().chain([String::new()]).iter()) ってやったけどなんかカッコ悪いかな…
判定はkeyの存在だけ確認すれば良いので HashSet に入れて「8つ揃っている」もしくは「7つだけ揃っていて cidが含まれていない」で判定。

use std::collections::HashMap;

struct Solution {
    inputs: Vec<String>,
}

impl Solution {
    fn new(inputs: Vec<String>) -> Self {
        Self { inputs }
    }
    fn solve_1(&self) -> usize {
        let mut ret = 0;
        let mut keys: HashSet<String> = HashSet::new();
        for line in self.inputs.iter().chain([String::new()].iter()) {
            if line.is_empty() {
                if keys.len() == 8 || (keys.len() == 7 && !keys.contains("cid")) {
                    ret += 1;
                }
                keys.clear();
            } else {
                keys.extend(
                    line.split(' ')
                        .filter_map(|field| field.split(':').next())
                        .map(|key| key.to_string()),
                );
            }
        }
        ret
    }
}

part2

今度はkeyの存在確認だけではなく、valueも正しい値であることを確認する必要がある。ひたすらその条件の通りに実装していくだけ。

part1ではHashSetにkeyだけを入れていたが、今度はvalueも見る必要があるのでHashMapに変更。valueを判定するかどうかで分岐を入れて、判定する場合はすべてのfieldについてそれぞれ条件に当てはまっているかを確認。iter().all(...)trueになれば良い。
lazy_staticって使ったことなくてよく分かっていない…

use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashMap;

impl Solution {
    fn new(inputs: Vec<String>) -> Self {
        Self { inputs }
    }
    fn solve_1(&self) -> usize {
        self.count_valid(false)
    }
    fn solve_2(&self) -> usize {
        self.count_valid(true)
    }
    fn count_valid(&self, validate_value: bool) -> usize {
        let mut ret = 0;
        let mut fields: HashMap<String, String> = HashMap::new();
        for line in self.inputs.iter().chain([String::new()].iter()) {
            if line.is_empty() {
                if (fields.len() == 8 || (fields.len() == 7 && !fields.contains_key("cid")))
                    && (!validate_value || self.validate_values(&fields))
                {
                    ret += 1;
                }
                fields.clear();
            } else {
                fields.extend(line.split(' ').map(|field| {
                    let v: Vec<&str> = field.split(':').collect();
                    (v[0].to_string(), v[1].to_string())
                }));
            }
        }
        ret
    }
    fn validate_values(&self, fields: &HashMap<String, String>) -> bool {
        fields.iter().all(|(key, value)| match key.as_str() {
            "byr" => {
                if let Ok(y) = value.parse::<i32>() {
                    1920 <= y && y <= 2002
                } else {
                    false
                }
            }
            "iyr" => {
                if let Ok(y) = value.parse::<i32>() {
                    2010 <= y && y <= 2020
                } else {
                    false
                }
            }
            "eyr" => {
                if let Ok(y) = value.parse::<i32>() {
                    2020 <= y && y <= 2030
                } else {
                    false
                }
            }
            "hgt" => {
                lazy_static! {
                    static ref RE: Regex = Regex::new(r"^(\d+)(cm|in)$").unwrap();
                }
                if let Some(cap) = RE.captures_iter(value).next() {
                    let n: i32 = cap[1].parse::<i32>().unwrap();
                    match &cap[2] {
                        "cm" => 150 <= n && n <= 193,
                        "in" => 59 <= n && n <= 76,
                        _ => false,
                    }
                } else {
                    false
                }
            }
            "hcl" => {
                lazy_static! {
                    static ref RE: Regex = Regex::new(r"^#[0-9a-f]{6}$").unwrap();
                }
                RE.is_match(value)
            }
            "ecl" => matches!(
                value.as_str(),
                "amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth"
            ),
            "pid" => value.len() == 9 && value.chars().all(|c| c.is_numeric()),
            "cid" => true,
            _ => false,
        })
    }
}

この記事に贈られたバッジ