aoc2020/day4.ml

206 lines
5.9 KiB
OCaml

type field = string * string
let fields0 str =
let drop k str = String.(sub str k (length str - k)) in
let get str =
if str = "" then None else
Scanf.sscanf str "%3s : %s %n"
(fun k v len -> Some ((k, v), drop len str)) in
Seq.unfold get str
let%test_module _ = (module struct
let fields' str = List.of_seq (fields0 str)
let%test "ε" = fields' "" = []
let%test "xxx:b" = fields' "xxx:b" = [("xxx", "b")]
let%test "abc:x def:y" = fields' "abc:x def:y" = [("abc", "x"); ("def", "y")]
end)
module StrSet = Set.Make(String)
let optional_fields = StrSet.singleton "cid"
let needed_fields =
StrSet.of_list ["byr"; "iyr"; "eyr"; "hgt"; "hcl"; "ecl"; "pid"]
let all_fields = StrSet.add "cid" needed_fields
let field_keys str =
fields0 str |> Seq.map fst |> StrSet.of_seq
let valid_set set =
StrSet.subset needed_fields set &&
StrSet.subset set all_fields
let%test_module _ = (module struct
let valid_list lst = List.to_seq lst |> StrSet.of_seq |> valid_set
let%test "ex1" = valid_list
["ecl"; "pid"; "eyr"; "hcl"; "byr"; "iyr"; "cid"; "hgt"]
let%test "ex2" = not @@ valid_list
["iyr"; "ecl"; "cid"; "eyr"; "pid"; "hcl"; "byr"]
let%test "ex3" = valid_list
["hcl"; "iyr"; "eyr"; "ecl"; "pid"; "byr"; "hgt"]
let%test "ex4" = not @@ valid_list
["hcl"; "eyr"; "pid"; "iyr"; "ecl"; "hgt"]
end)
let main' make valid file =
let items = Bracket.infile_chunks file ~chunk:make ~of_seq:List.of_seq in
let len_all = List.length items in
let len_valid = List.(length (filter valid items)) in
Printf.printf "%d valid out of %d\n" len_valid len_all
let main1 = main' field_keys valid_set
let%expect_test "part 1" =
main1 "../../data/day4";
[%expect{| 233 valid out of 288 |}]
module StrMap = Map.Make(String)
module Validators = struct
let (=~?) str rx =
Re.Pcre.(pmatch ~rex:(regexp rx) str)
let (=~) str rx =
try Some (Re.Pcre.(extract ~rex:(regexp rx) str))
with Not_found -> None
let year_range lo hi x =
x =~? {|^\d{4}$|} &&
let x = int_of_string x in
x >= lo && x <= hi
let%test _ = year_range 1900 2000 "1950"
let%test _ = year_range 1900 2000 "1900"
let%test _ = year_range 1900 2000 "2000"
let%test _ = not (year_range 1900 2000 "0950")
let%test _ = not (year_range 1900 2000 "01950")
let%test _ = not (year_range 1900 2000 "")
let%test _ = not (year_range 1900 2000 "lmao")
let%test _ = not (year_range 1900 2000 "1950aaaa")
let height str =
match str =~ {|^(\d+)(cm|in)$|} with
| Some res ->
let num = int_of_string res.(1) in
if res.(2) = "cm" then
num >= 150 && num <= 193
else
num >= 59 && num <= 76
| None -> false
let%test _ = height "150cm"
let%test _ = height "175cm"
let%test _ = height "193cm"
let%test _ = not (height "200cm") (* hmmmmmmmmmm *)
let%test _ = not (height "50cm")
let%test _ = not (height "1500mm")
let%test _ = height "59in"
let%test _ = height "69in"
let%test _ = height "76in"
let%test _ = not (height "6in")
let%test _ = not (height "600in")
let%test _ = not (height "5ft")
let%test _ = not (height "")
let hex_col str = str =~? {|^#[0-9a-f]{6}$|}
let%test _ = hex_col "#000000"
let%test _ = not (hex_col "a#000000")
let%test _ = not (hex_col "#000000a")
let%test _ = hex_col "#abcabc"
let%test _ = not (hex_col "#ABCABC") (* according to the spec anyway *)
let eye_col str = str =~? {|^(amb|blu|brn|gry|grn|hzl|oth)$|}
let%test _ = eye_col "blu"
let%test _ = not (eye_col "blue")
let%test _ = not (eye_col "")
let pid str = str =~? {|^[0-9]{9}$|}
let%test _ = pid "012345678"
let%test _ = pid "123456789"
let%test _ = not (pid "12345678")
let%test _ = not (pid "1234567890")
let map = StrMap.of_seq @@ List.to_seq
["byr", year_range 1920 2002;
"iyr", year_range 2010 2020;
"eyr", year_range 2020 2030;
"hgt", height;
"hcl", hex_col;
"ecl", eye_col;
"pid", pid;
"cid", fun _ -> true]
let validate k v =
StrMap.find_opt k map
|> Option.fold ~none:false ~some:(fun f -> f v)
end
let validate = Validators.validate
let fields str = fields0 str |> StrMap.of_seq
let valid_keys map =
StrMap.to_seq map |> Seq.map fst |> StrSet.of_seq |> valid_set
let valid_map map = valid_keys map && StrMap.for_all validate map
let%test_module _ = (module struct
let valid_list lst = List.to_seq lst |> StrMap.of_seq |> valid_map
let%test "ex1" = not @@ valid_list
[("eyr", "1972"); ("cid", "100"); ("hcl", "#18171d"); ("ecl", "amb");
("hgt", "170"); ("pid", "186cm"); ("iyr", "2018"); ("byr", "1926")]
let%test "ex2" = not @@ valid_list
[("iyr", "2019"); ("hcl", "#602927"); ("eyr", "1967"); ("hgt", "170cm");
("ecl", "grn"); ("pid", "012533040"); ("byr", "1946")]
let%test "ex3" = not @@ valid_list
[("hcl", "dab227"); ("iyr", "2012"); ("ecl", "brn"); ("hgt", "182cm");
("pid", "021572410"); ("eyr", "2020"); ("byr", "1992"); ("cid", "277")]
let%test "ex4" = not @@ valid_list
[("hgt", "59cm"); ("ecl", "zzz"); ("eyr", "2038"); ("hcl", "74454a");
("iyr", "2023"); ("pid", "3556412378"); ("byr", "2007")]
let%test "ex5" = valid_list
[("pid", "087499704"); ("hgt", "74in"); ("ecl", "grn"); ("iyr", "2012");
("eyr", "2030"); ("byr", "1980"); ("hcl", "#623a2f")]
let%test "ex6" = valid_list
[("eyr", "2029"); ("ecl", "blu"); ("cid", "129"); ("byr", "1989");
("iyr", "2014"); ("pid", "896056539"); ("hcl", "#a97842");
("hgt", "165cm")]
let%test "ex7" = valid_list
[("hcl", "#888785"); ("hgt", "164cm"); ("byr", "2001"); ("iyr", "2015");
("cid", "88"); ("pid", "545766238"); ("ecl", "hzl"); ("eyr", "2022")]
let%test "ex8" = valid_list
[("iyr", "2010"); ("hgt", "158cm"); ("hcl", "#b6652a"); ("ecl", "blu");
("byr", "1944"); ("eyr", "2021"); ("pid", "093154719")]
end)
let main2 = main' fields valid_map
let%expect_test _ =
main2 "../../data/day4";
[%expect{| 111 valid out of 288 |}]
let main = Misc.main 4 [|main1; main2|]