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)