DEV Community

Discussion on: Advent of Code 2020 Solution Megathread - Day 4: Passport Processing

Collapse
 
benwtrent profile image
Benjamin Trent

My super gross rust impl. Messy regex. I am sure there are way better ways to parse according to white space.

Also, I sort of went half-way with a struct solution. Probably would have been better to either go all the way or not have custom types at all.

#[derive(Debug)]
struct Passport {
    birth_year: Option<usize>,
    issue_year: Option<usize>,
    exp_year: Option<usize>,
    height: Option<String>,
    hair_color: Option<String>,
    eye_color: Option<String>,
    pid: Option<String>,
    cid: Option<usize>,
}

impl From<&str> for Passport {
    fn from(s: &str) -> Self {
        let (
            mut birth_year,
            mut issue_year,
            mut exp_year,
            mut height,
            mut hair_color,
            mut eye_color,
            mut pid,
            mut cid,
        ) = (
            Option::None,
            Option::None,
            Option::None,
            Option::None,
            Option::None,
            Option::None,
            Option::None,
            Option::None,
        );
        s.split(" ").filter(|i| !i.is_empty()).for_each(|i| {
            let name_var: Vec<&str> = i.split(":").collect();
            match name_var[0] {
                "byr" => birth_year = Option::Some(name_var[1].parse().unwrap()),
                "iyr" => issue_year = Option::Some(name_var[1].parse().unwrap()),
                "eyr" => exp_year = Option::Some(name_var[1].parse().unwrap()),
                "hgt" => height = Option::Some(String::from(name_var[1])),
                "hcl" => hair_color = Option::Some(String::from(name_var[1])),
                "ecl" => eye_color = Option::Some(String::from(name_var[1])),
                "pid" => pid = Option::Some(String::from(name_var[1])),
                "cid" => cid = Option::Some(name_var[1].parse().unwrap()),
                _ => {}
            }
        });
        Passport {
            birth_year,
            issue_year,
            exp_year,
            height,
            hair_color,
            eye_color,
            pid,
            cid,
        }
    }
}

impl Passport {
    pub fn is_valid(&self) -> bool {
        self.birth_year.is_some()
            && self.issue_year.is_some()
            && self.exp_year.is_some()
            && self.height.is_some()
            && self.hair_color.is_some()
            && self.eye_color.is_some()
            && self.pid.is_some()
    }

    pub fn is_valid_strict(&self) -> bool {
        self.valid_birth_year()
            && self.valid_issue_year()
            && self.valid_exp_year()
            && self.valid_hgt()
            && self.valid_hair()
            && self.valid_eyes()
            && self.valid_pid()
    }

    fn valid_birth_year(&self) -> bool {
        (1920..=2002).contains(&self.birth_year.unwrap_or_default())
    }

    fn valid_issue_year(&self) -> bool {
        (2010..=2020).contains(&self.issue_year.unwrap_or_default())
    }

    fn valid_exp_year(&self) -> bool {
        (2020..=2030).contains(&self.exp_year.unwrap_or_default())
    }

    fn valid_hgt(&self) -> bool {
        if let Some(height) = self.height.as_ref() {
            let range = match &height[height.len() - 2..] {
                "in" => (59..=76),
                "cm" => (150..=193),
                _ => return false,
            };
            range.contains(&height[0..height.len() - 2].parse::<usize>().unwrap_or(0))
        } else {
            false
        }
    }

    fn valid_hair(&self) -> bool {
        Passport::valid_str(self.hair_color.as_ref(), r"^#[0-9a-f]{6}$")
    }

    fn valid_eyes(&self) -> bool {
        Passport::valid_str(self.eye_color.as_ref(), r"^amb|blu|brn|gry|grn|hzl|oth$")
    }

    fn valid_pid(&self) -> bool {
        Passport::valid_str(self.pid.as_ref(), r"^[0-9]{9}$")
    }

    fn valid_str(maybe_str: Option<&String>, re: &str) -> bool {
        if let Some(str) = maybe_str {
            let re = regex::Regex::new(re).unwrap();
            let captures = re.captures(str.as_str());
            captures.is_some()
        } else {
            false
        }
    }
}

#[aoc_generator(day4)]
fn input_to_vec(input: &str) -> Vec<Passport> {
    let mut cleaned_str = String::from("");
    let mut cleaned_input: Vec<Passport> = vec![];
    input.lines().for_each(|i| {
        if i.is_empty() && !cleaned_str.is_empty() {
            cleaned_input.push(Passport::from(cleaned_str.as_str()));
            cleaned_str = String::from("");
        }
        cleaned_str += i;
        cleaned_str += " ";
    });
    if !cleaned_str.is_empty() {
        cleaned_input.push(Passport::from(cleaned_str.as_str()));
    }
    cleaned_input
}

#[aoc(day4, part1)]
fn valid_count(input: &Vec<Passport>) -> usize {
    input.iter().filter(|p| p.is_valid()).count()
}

#[aoc(day4, part2)]
fn strict_valid_count(input: &Vec<Passport>) -> usize {
    input.iter().filter(|p| p.is_valid_strict()).count()
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ballpointcarrot profile image
Christopher Kruse

You're not alone in the ugly feeling. I had a particularly nasty bug that gave me one result too many in part two (realized I had a missing ^ and $ on the regex for pid).

I see a fair amount of similarities in approach, so I'm glad to see I'm in good company. :D

As always, on Github.

use aoc_runner_derive::{aoc, aoc_generator};
use regex::Regex;

#[derive(Debug, PartialEq)]
struct Height {
    measure: usize,
    unit: String,
}

impl Height {
    fn parse(hgt_str: &str) -> Option<Height> {
        let re = Regex::new("(\\d+)(in|cm)").expect("Unable to create Regex");
        match re.captures(hgt_str) {
            None => None,
            Some(captures) => {
                let h = Height {
                    measure: str::parse(captures.get(1).unwrap().as_str())
                        .expect("Unable to parse number"),
                    unit: String::from(captures.get(2).unwrap().as_str()),
                };
                Some(h)
            }
        }
    }
    fn is_valid(&self) -> bool {
        match self.unit.as_str() {
            "cm" => self.measure >= 150 && self.measure <= 193,
            "in" => self.measure >= 59 && self.measure <= 76,
            _ => panic!("Not a valid unit"),
        }
    }
}

#[derive(Debug, PartialEq)]
struct Passport {
    byr: Option<usize>,
    iyr: Option<usize>,
    eyr: Option<usize>,
    hgt: Option<Height>,
    hgt_str: Option<String>,
    hcl: Option<String>,
    ecl: Option<String>,
    pid: Option<String>,
    cid: Option<String>,
}

impl Passport {
    fn new() -> Passport {
        Passport {
            byr: None,
            iyr: None,
            eyr: None,
            hgt: None,
            hgt_str: None,
            hcl: None,
            ecl: None,
            pid: None,
            cid: None,
        }
    }

    fn has_fields(&self) -> bool {
        self.byr.is_some()
            && self.iyr.is_some()
            && self.eyr.is_some()
            && self.hgt_str.is_some()
            && self.hcl.is_some()
            && self.ecl.is_some()
            && self.pid.is_some()
    }

    fn is_valid(&self) -> bool {
        self.valid_byr()
            && self.valid_iyr()
            && self.valid_eyr()
            && self.valid_hgt()
            && self.valid_hcl()
            && self.valid_ecl()
            && self.valid_pid()
    }

    fn valid_byr(&self) -> bool {
        match self.byr {
            None => false,
            Some(n) => n >= 1920 && n <= 2002,
        }
    }
    fn valid_iyr(&self) -> bool {
        match self.iyr {
            None => false,
            Some(n) => n >= 2010 && n <= 2020,
        }
    }
    fn valid_eyr(&self) -> bool {
        match self.eyr {
            None => false,
            Some(n) => n >= 2020 && n <= 2030,
        }
    }
    fn valid_hgt(&self) -> bool {
        match &self.hgt {
            None => false,
            Some(h) => h.is_valid(),
        }
    }
    fn valid_hcl(&self) -> bool {
        let re = Regex::new("^#[0-9a-f]{6}$").expect("Failed to make regex");
        match &self.hcl {
            None => false,
            Some(hair) => re.is_match(hair.as_str()),
        }
    }
    fn valid_ecl(&self) -> bool {
        let valid_colors = vec!["amb", "blu", "brn", "gry", "grn", "hzl", "oth"];
        match &self.ecl {
            None => false,
            Some(c) => valid_colors.contains(&c.as_str()),
        }
    }
    fn valid_pid(&self) -> bool {
        let re = Regex::new("^[0-9]{9}$").expect("Failed to build Regex");
        match &self.pid {
            None => false,
            Some(pid) => re.is_match(pid.as_str()),
        }
    }
}

#[aoc_generator(day4)]
fn parse_input_day4(input: &str) -> Vec<Passport> {
    input
        .split("\n\n")
        .map(|passport_str| parse_passport(passport_str))
        .collect()
}

fn parse_passport(passport_str: &str) -> Passport {
    let kv: Vec<&str> = passport_str
        .lines()
        .flat_map(|line| line.split(" "))
        .collect();
    let mut pass = Passport::new();
    for key_val in kv {
        let pair: Vec<&str> = key_val.split(":").collect();
        match *(pair.get(0).unwrap()) {
            "cid" => pass.cid = Some(String::from(*pair.get(1).unwrap())),
            "byr" => pass.byr = Some(str::parse(*pair.get(1).unwrap()).unwrap()),
            "iyr" => pass.iyr = Some(str::parse(*pair.get(1).unwrap()).unwrap()),
            "eyr" => pass.eyr = Some(str::parse(*pair.get(1).unwrap()).unwrap()),
            "hgt" => {
                pass.hgt_str = Some(str::parse(*pair.get(1).unwrap()).unwrap());
                pass.hgt = Height::parse(*pair.get(1).unwrap());
            }
            "hcl" => pass.hcl = Some(String::from(*pair.get(1).unwrap())),
            "ecl" => pass.ecl = Some(String::from(*pair.get(1).unwrap())),
            "pid" => pass.pid = Some(String::from(*pair.get(1).unwrap())),
            _ => panic!("Found passport code that doesn't match"),
        }
    }
    pass
}

#[aoc(day4, part1)]
fn count_valid_passports(input: &Vec<Passport>) -> usize {
    input.iter().filter(|pass| pass.has_fields()).count()
}

#[aoc(day4, part2)]
fn count_valid_data_passports(input: &Vec<Passport>) -> usize {
    input.iter().filter(|pass| pass.is_valid()).count()
}
Enter fullscreen mode Exit fullscreen mode