let read_lines path = let lines = ref [] in let chan = open_in path in try while true; do lines := input_line chan :: !lines done; !lines with End_of_file -> close_in chan; List.rev !lines type packet_body = | Simple of int | Compound of packet list and packet = { version: int; packet_type: packet_type; body: packet_body; } and packet_type = | Sum | Product | Minimum | Maximum | Literal | Greater | Lesser | Equal let hexadecimal_digit_to_binary = function | '0' -> "0000" | '1' -> "0001" | '2' -> "0010" | '3' -> "0011" | '4' -> "0100" | '5' -> "0101" | '6' -> "0110" | '7' -> "0111" | '8' -> "1000" | '9' -> "1001" | 'A' -> "1010" | 'B' -> "1011" | 'C' -> "1100" | 'D' -> "1101" | 'E' -> "1110" | 'F' -> "1111" | _ -> assert false let parse_packet_type = function | 0 -> Sum | 1 -> Product | 2 -> Minimum | 3 -> Maximum | 4 -> Literal | 5 -> Greater | 6 -> Lesser | 7 -> Equal | _ -> assert false let hexadecimal_to_binary hex = let buffer = Buffer.create ((String.length hex) * 4) in let expand_digit digit = Buffer.add_string buffer (hexadecimal_digit_to_binary digit) in String.iter expand_digit hex; Buffer.contents buffer let parse input = let data = hexadecimal_to_binary input in let rec binary_to_decimal ?(init = 0) start length = let stop = start + length in let rec parse acc position = if position < stop then parse (acc * 2 + (Char.code data.[position] - (Char.code '0'))) (position + 1) else acc in parse init start and parse_bytes start length = (binary_to_decimal start length, start + length) and parse_literal start = let rec aux position value = let value = binary_to_decimal ~init:value (position + 1) 4 in let next_pos = position + 5 in if data.[position] == '1' then aux next_pos value else (value, next_pos) in aux start 0 and parse_n_subpackets start = let rec aux acc position n = match n with | 0 -> (List.rev acc, position) | _ -> let (packet, next) = parse_packet position in aux (packet :: acc) next (n - 1) in aux [] (start + 11) (binary_to_decimal start 11) and parse_packet position = let (version, next) = parse_bytes position 3 in let (packet_t, next) = parse_bytes next 3 in let (body, next) = match packet_t with | 4 -> let (value, next) = parse_literal next in (Simple value, next) | _ -> let (value, next) = parse_subpacket next in (Compound value, next) in ({ version = version; body = body; packet_type = parse_packet_type packet_t }, next) and parse_subpacket start = let (length_t, next) = parse_bytes start 1 in match length_t with | 0 -> parse_n_bytes_of_subpackets next | 1 -> parse_n_subpackets next | _ -> assert false and parse_n_bytes_of_subpackets start = let (count, next) = parse_bytes start 15 in let limit = count + next in let rec aux acc position = if position >= limit then (List.rev acc, position) else let (packet, next) = parse_packet position in aux (packet :: acc) next in aux [] next in let (packet, _) = parse_packet 0 in packet let rec packet_version_sum packet = match packet.body with | Simple _ -> packet.version | Compound subpackets -> packet.version + (subpackets |> List.map packet_version_sum |> List.fold_left (+) 0) let rec evaluate_packet packet = match packet.packet_type with | Literal -> (match packet.body with Simple x -> x | _ -> assert false) | Sum -> fold_left_subpackets (+) packet | Product -> fold_left_subpackets ( * ) packet | Minimum -> fold_left_subpackets min packet | Maximum -> fold_left_subpackets max packet | Greater -> subpacket_compare (>) packet | Lesser -> subpacket_compare (<) packet | Equal -> subpacket_compare (=) packet and evaluate_subpackets packet = packet |> subpackets |> List.map evaluate_packet and fold_left_subpackets f packet = let subs = evaluate_subpackets packet in List.fold_left f (List.hd subs) (List.tl subs) and subpacket_compare f packet = match evaluate_subpackets packet with | a :: b :: [] -> if f a b then 1 else 0 | _ -> assert false and subpackets packet = match packet.body with | Compound x -> x | _ -> assert false let part1 = packet_version_sum let part2 = evaluate_packet let packet = read_lines Sys.argv.(1) |> List.hd |> parse let () = Printf.printf "Part 1: %d\n" (part1 packet) let () = Printf.printf "Part 2: %d\n" (part2 packet)