(* 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);;