#!nix-shell --pure -i ocaml -p ocaml
*)
module RegisterMap = Map.Make (Char)
type input = {registers: int RegisterMap.t; program: int list}
let input =
let initial_state = {registers= RegisterMap.empty; program= []} in
let parse_register input state =
match String.split_on_char ' ' input with
| ["Register"; register; value] ->
{ state with
registers=
RegisterMap.add register.[0] (int_of_string value) state.registers
}
| _ ->
failwith (Printf.sprintf "bad registers: %s" input)
in
let parse_program input state =
match String.split_on_char ' ' input with
| ["Program:"; program] ->
{ state with
program= program |> String.split_on_char ',' |> List.map int_of_string
}
| _ ->
failwith (Printf.sprintf "bad program: %s" input)
in
let rec read_input reading_registers state =
try
match (read_line (), reading_registers) with
| "", _ ->
read_input false state
| v, true ->
parse_register v state |> read_input true
| v, false ->
parse_program v state |> read_input false
with End_of_file -> state
in
read_input true initial_state
type instruction = ADV | BXL | BST | JNZ | BXC | OUT | BDV | CDV
let instruction_of = function
| 0 ->
ADV
| 1 ->
BXL
| 2 ->
BST
| 3 ->
JNZ
| 4 ->
BXC
| 5 ->
OUT
| 6 ->
BDV
| 7 ->
CDV
| _ ->
failwith "invalid instruction"
let read_register registers = function
| (0 | 1 | 2 | 3) as x ->
x
| 4 ->
RegisterMap.find 'A' registers
| 5 ->
RegisterMap.find 'B' registers
| 6 ->
RegisterMap.find 'C' registers
| _ ->
failwith "bad register"
let rec pow2 exp = if exp = 0 then 1 else 2 * pow2 (exp - 1)
let take_after n arr =
let rec aux n arr =
match (n, arr) with
| 0, x :: xs ->
x :: aux 0 xs
| n, x :: xs ->
aux (n - 1) xs
| _, [] ->
[]
in
aux n arr
let rec run_program program registers =
let execute instr operand xs =
match instruction_of instr with
| (ADV | BDV | CDV) as instr ->
let numerator, raw_denominator =
(RegisterMap.find 'A' registers, read_register registers operand)
in
let output_register =
match instr with
| ADV ->
'A'
| BDV ->
'B'
| CDV ->
'C'
| _ ->
failwith "unreachable"
in
RegisterMap.add output_register
(numerator / pow2 raw_denominator)
registers
|> run_program xs
| BXL ->
let base = RegisterMap.find 'B' registers in
RegisterMap.add 'B' (base lxor operand) registers |> run_program xs
| BST ->
let base = read_register registers operand in
RegisterMap.add 'B' (base land 0b111) registers |> run_program xs
| JNZ ->
let xs =
if RegisterMap.find 'A' registers = 0 then xs
else take_after operand input.program
in
run_program xs registers
| BXC ->
let b, c =
(RegisterMap.find 'B' registers, RegisterMap.find 'C' registers)
in
RegisterMap.add 'B' (b lxor c) registers |> run_program xs
| OUT ->
let ret = read_register registers operand |> ( land ) 0b111 in
Some (ret, xs, registers)
in
match program with
| instr :: operand :: xs ->
execute instr operand xs
| instr :: xs ->
failwith "bad opcodes"
| [] ->
None
let part1 =
let rec aux program registers =
match run_program program registers with
| Some (ret, program, registers) ->
ret :: aux program registers
| None ->
[]
in
aux input.program input.registers
|> List.map string_of_int |> String.concat "," |> print_endline
let part2 =
let build_register value = RegisterMap.add 'A' value input.registers in
let check_value expected_output value =
match run_program input.program (build_register value) with
| Some (value, _, _) ->
value = expected_output
| None ->
false
in
let try_digit acc x =
let possible_values = List.init 8 (fun i -> (acc lsl 3) lor i) in
List.find_opt (check_value x) possible_values
in
let rec build_solution acc = function
| [] ->
Some acc
| x :: xs ->
try_digit acc x |> Fun.flip Option.bind (Fun.flip build_solution xs)
in
input.program |> List.rev |> build_solution 0 |> Option.get |> print_int
|> print_newline