You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
92 lines
3.2 KiB
OCaml
92 lines
3.2 KiB
OCaml
(* Common util. Should probably put this in a module or something.. *)
|
|
let get_one_line file =
|
|
try Some (input_line file) with
|
|
End_of_file -> None
|
|
|
|
let get_lines file =
|
|
let rec input lines =
|
|
match get_one_line file with
|
|
Some line -> input (line :: lines)
|
|
| None -> List.rev lines
|
|
in input []
|
|
|
|
let read_file file =
|
|
let channel = open_in(file) in
|
|
get_lines channel
|
|
|
|
let explode s = (* convert a string to a list of chars *)
|
|
let rec exp i l =
|
|
if i < 0 then l else exp (i - 1) (s.[i] :: l) in
|
|
exp (String.length s - 1) []
|
|
|
|
let implode l = String.of_seq (List.to_seq l)
|
|
|
|
(* --- *)
|
|
|
|
let fix_lines lines = (* puts each passport on 1 line *)
|
|
let glue s = if String.equal s "" then "\n" else " " in (* which char to concat the lines *)
|
|
let fix_line acc cur = acc ^ (glue cur) ^ cur in (* our reducer: concat lines with either space or newline *)
|
|
List.fold_left fix_line "" lines
|
|
|
|
let read_passport line = (* converts a line to an alist *)
|
|
let chunks = String.(split_on_char ' ' (trim line)) in (* split each string on space *)
|
|
let read_chunk chunk =
|
|
let parts = String.split_on_char ':' chunk in (* split each chunk on colon *)
|
|
List.(nth parts 0, nth parts 1) in (* return a tuple for each chunk *)
|
|
List.map read_chunk chunks
|
|
|
|
let required_fields = ["byr"; "iyr"; "eyr"; "hgt"; "hcl"; "ecl"; "pid";]
|
|
|
|
let passport_has_required_fields passport =
|
|
let has_key key = (* does a passport have a particular key *)
|
|
match List.assoc_opt key passport with
|
|
| Some _ -> true
|
|
| _ -> false
|
|
in
|
|
List.for_all has_key required_fields (* does it have ALL the keys *)
|
|
|
|
let is_in_range (min, max) n = int_of_string n >= min && int_of_string n <= max
|
|
|
|
let is_height_valid h =
|
|
let len = String.length h in
|
|
let units = String.sub h (len - 2) 2 in
|
|
let value = String.sub h 0 (len - 2) in
|
|
match units with
|
|
| "cm" -> is_in_range (150, 193) value
|
|
| "in" -> is_in_range (59, 76) value
|
|
| _ -> false
|
|
|
|
let is_char_hex c =
|
|
let hex_chars = "0123456789abcdef" in
|
|
String.contains hex_chars c
|
|
|
|
let is_hair_valid h =
|
|
let color = (String.sub h 1 (String.length h - 1)) in
|
|
String.length h = 7 &&
|
|
String.sub h 0 1 = "#" &&
|
|
List.for_all is_char_hex (explode color)
|
|
|
|
let is_eye_color_valid c =
|
|
List.mem c ["amb"; "blu"; "brn"; "gry"; "grn"; "hzl"; "oth";]
|
|
|
|
let is_pid_valid p =
|
|
String.length p = 9 &&
|
|
List.for_all (fun c -> List.mem c (explode "0123456789")) (explode p)
|
|
|
|
let is_passport_valid p =
|
|
is_in_range (1920, 2002) (List.assoc "byr" p) &&
|
|
is_in_range (2010, 2020) (List.assoc "iyr" p) &&
|
|
is_in_range (2020, 2030) (List.assoc "eyr" p) &&
|
|
is_height_valid (List.assoc "hgt" p) &&
|
|
is_hair_valid (List.assoc "hcl" p) &&
|
|
is_eye_color_valid (List.assoc "ecl" p) &&
|
|
is_pid_valid (List.assoc "pid" p)
|
|
|
|
let lines = read_file "day4.txt" |> fix_lines |> String.split_on_char '\n'
|
|
let passports = List.map read_passport lines
|
|
let passports_with_required_fields = List.filter passport_has_required_fields passports
|
|
let valid_passports = List.filter is_passport_valid passports_with_required_fields;;
|
|
|
|
Printf.printf "Number of passports with all required fields: %i\n" (List.length passports_with_required_fields);;
|
|
Printf.printf "Number of valid passports: %i\n" (List.length valid_passports);;
|