Passport Processing
defmodule Aoc2020.Day4 do
@moduledoc "Passport Processing"
def parse_passport(str) do
str
|> String.replace("\n", " ")
|> String.split(" ")
|> Enum.map(&String.split(&1, ":"))
|> Enum.map(fn [k, v] -> {String.to_atom(k), v} end)
end
def parse_input() do
File.read!("priv/inputs/2020/day4.txt")
|> String.trim()
|> String.split("\n\n")
|> Enum.map(&parse_passport/1)
end
def split_into_passports(lst) do
chunk_fun = fn element, acc ->
if element == "|" do
{:cont, Enum.reverse(acc), []}
else
{:cont, [element | acc]}
end
end
after_fun = fn
[] -> {:cont, []}
acc -> {:cont, Enum.reverse(acc), []}
end
Enum.chunk_while(lst, [], chunk_fun, after_fun)
end
def drop_cid(passport) do
Enum.filter(passport, fn {k, _} -> k != :cid end)
end
# part 2
def between(value, min, max) do
String.to_integer(value) >= min && String.to_integer(value) <= max
end
def valid_birthyear(passport) do
Keyword.has_key?(passport, :byr) && between(passport[:byr], 1920, 2002)
end
def valid_issue_year(passport) do
Keyword.has_key?(passport, :iyr) && between(passport[:iyr], 2010, 2020)
end
def valid_expiration_year(passport) do
Keyword.has_key?(passport, :eyr) && between(passport[:eyr], 2020, 2030)
end
def valid_height(passport) do
if passport[:hgt] do
with [_, num, unit] <- Regex.run(~r/(\d+)(cm|in)/, passport[:hgt]) do
case unit do
"cm" -> between(num, 150, 193)
"in" -> between(num, 59, 76)
_ -> false
end
else
nil -> false
end
else
false
end
end
def valid_hair_colour(passport) do
Keyword.has_key?(passport, :hcl) && String.match?(passport[:hcl], ~r/^#[0-9a-f]{6}$/)
end
def valid_eye_colour(passport) do
Keyword.has_key?(passport, :ecl) &&
Enum.member?(~w(amb blu brn gry grn hzl oth), passport[:ecl])
end
def valid_passport_id(passport) do
Keyword.has_key?(passport, :pid) && String.match?(passport[:pid], ~r/^[0-9]{9}$/)
end
def part_1 do
parse_input()
|> Enum.map(&drop_cid/1)
|> length
end
def part_2 do
validators = [
&valid_birthyear/1,
&valid_issue_year/1,
&valid_expiration_year/1,
&valid_height/1,
&valid_hair_colour/1,
&valid_eye_colour/1,
&valid_passport_id/1
]
parse_input()
|> Enum.map(&drop_cid/1)
|> Enum.filter(fn passport -> Enum.all?(validators, &apply(&1, [passport])) end)
|> length
end
def run do
part_2()
end
end