summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/2020/4/mod.rs132
-rw-r--r--src/2020/mod.rs4
-rw-r--r--src/util.rs11
3 files changed, 146 insertions, 1 deletions
diff --git a/src/2020/4/mod.rs b/src/2020/4/mod.rs
new file mode 100644
index 0000000..4b51b29
--- /dev/null
+++ b/src/2020/4/mod.rs
@@ -0,0 +1,132 @@
+use anyhow::Context as _;
+
+const REQUIRED_KEYS: &[&str] =
+ &["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"];
+
+pub fn part1() -> anyhow::Result<()> {
+ let batch = crate::util::read_file_str("data/4.txt")?;
+ let mut valid = 0;
+ for passport in parse(&batch)? {
+ let mut cur_valid = true;
+ for key in REQUIRED_KEYS {
+ if !passport.contains_key(&key.to_string()) {
+ cur_valid = false;
+ break;
+ }
+ }
+ if cur_valid {
+ valid += 1;
+ }
+ }
+ println!("{}", valid);
+ Ok(())
+}
+
+pub fn part2() -> anyhow::Result<()> {
+ let batch = crate::util::read_file_str("data/4.txt")?;
+ let mut valid = 0;
+ for passport in parse(&batch)? {
+ let mut cur_valid = true;
+ for key in REQUIRED_KEYS {
+ match passport.get(&key.to_string()) {
+ Some(val) => {
+ if !validate(key, val)? {
+ cur_valid = false;
+ break;
+ }
+ }
+ None => {
+ cur_valid = false;
+ break;
+ }
+ }
+ }
+ if cur_valid {
+ valid += 1;
+ }
+ }
+ println!("{}", valid);
+ Ok(())
+}
+
+fn parse(
+ batch: &str,
+) -> anyhow::Result<Vec<std::collections::HashMap<String, String>>> {
+ let mut res = vec![];
+ let mut cur = std::collections::HashMap::new();
+ for line in batch.lines() {
+ if line.is_empty() {
+ res.push(cur);
+ cur = std::collections::HashMap::new();
+ continue;
+ }
+
+ for field in line.split(' ') {
+ let mut parts = field.split(':');
+ let key = parts.next().with_context(|| {
+ format!("failed to parse field '{}'", field)
+ })?;
+ let value = parts.next().with_context(|| {
+ format!("failed to parse field '{}'", field)
+ })?;
+ cur.insert(key.to_string(), value.to_string());
+ }
+ }
+ if !cur.is_empty() {
+ res.push(cur);
+ }
+ Ok(res)
+}
+
+fn validate(key: &str, val: &str) -> anyhow::Result<bool> {
+ match key {
+ "byr" => match val.parse::<i32>() {
+ Ok(year) => Ok(year >= 1920 && year <= 2002),
+ Err(_) => Ok(false),
+ },
+ "iyr" => match val.parse::<i32>() {
+ Ok(year) => Ok(year >= 2010 && year <= 2020),
+ Err(_) => Ok(false),
+ },
+ "eyr" => match val.parse::<i32>() {
+ Ok(year) => Ok(year >= 2020 && year <= 2030),
+ Err(_) => Ok(false),
+ },
+ "hgt" => {
+ if val.len() < 3 {
+ Ok(false)
+ } else if val.ends_with("in") {
+ match val[0..val.len() - 2].parse::<i32>() {
+ Ok(inches) => Ok(inches >= 59 && inches <= 76),
+ Err(_) => Ok(false),
+ }
+ } else if val.ends_with("cm") {
+ match val[0..val.len() - 2].parse::<i32>() {
+ Ok(inches) => Ok(inches >= 150 && inches <= 193),
+ Err(_) => Ok(false),
+ }
+ } else {
+ Ok(false)
+ }
+ }
+ "hcl" => Ok(val.len() == 7
+ && val.starts_with('#')
+ && val[1..]
+ == val[1..]
+ .matches(|c: char| c.is_ascii_hexdigit())
+ .collect::<String>()),
+ "ecl" => Ok(val == "amb"
+ || val == "blu"
+ || val == "brn"
+ || val == "gry"
+ || val == "grn"
+ || val == "hzl"
+ || val == "oth"),
+ "pid" => Ok(val.len() == 9
+ && val
+ == val
+ .matches(|c: char| c.is_ascii_digit())
+ .collect::<String>()),
+ _ => Err(anyhow::anyhow!("invalid key found: {}", key)),
+ }
+}
diff --git a/src/2020/mod.rs b/src/2020/mod.rs
index ff08a7e..812a379 100644
--- a/src/2020/mod.rs
+++ b/src/2020/mod.rs
@@ -4,6 +4,8 @@ mod day1;
mod day2;
#[path = "3/mod.rs"]
mod day3;
+#[path = "4/mod.rs"]
+mod day4;
pub fn run(day: u8, puzzle: u8) -> anyhow::Result<()> {
match (day, puzzle) {
@@ -13,6 +15,8 @@ pub fn run(day: u8, puzzle: u8) -> anyhow::Result<()> {
(2, 2) => day2::part2(),
(3, 1) => day3::part1(),
(3, 2) => day3::part2(),
+ (4, 1) => day4::part1(),
+ (4, 2) => day4::part2(),
_ => Err(anyhow::anyhow!("unknown puzzle {}-{}", day, puzzle)),
}
}
diff --git a/src/util.rs b/src/util.rs
index fdc3bed..94f9796 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,5 +1,5 @@
use anyhow::Context as _;
-use std::io::BufRead as _;
+use std::io::{BufRead as _, Read as _};
pub fn read_ints(filename: &str) -> anyhow::Result<Vec<i32>> {
let f = std::fs::File::open(filename)
@@ -15,3 +15,12 @@ pub fn read_ints(filename: &str) -> anyhow::Result<Vec<i32>> {
.collect();
ints
}
+
+pub fn read_file_str(filename: &str) -> anyhow::Result<String> {
+ let mut f = std::fs::File::open(filename)
+ .with_context(|| format!("couldn't find data file {}", filename))?;
+ let mut s = String::new();
+ f.read_to_string(&mut s)
+ .context("failed to read map contents")?;
+ Ok(s)
+}