@@ 0,0 1,124 @@
+-module(sol).
+-compile(export_all).
+
+-record(monkey, {items, op, test, if_true, if_false}).
+
+parse(Lines) ->
+ parse_init(Lines, []).
+
+parse_init([], Monkeys) ->
+ lists:reverse(Monkeys);
+parse_init(["" | Rest], Monkeys) ->
+ parse_init(Rest, Monkeys);
+parse_init([Line | Rest], Monkeys) ->
+ {match, _} = re:run(Line, "Monkey (\\d+):"),
+ parse_starting_items(Rest, Monkeys).
+
+parse_starting_items([Line | Rest], Monkeys) ->
+ {" Starting items: ", ItemsStr} = lists:split(18, Line),
+ Items = [list_to_integer(X) || X <- string:split(ItemsStr, ", ", all)],
+ parse_operation(Rest, Items, Monkeys).
+
+parse_operation([Line | Rest], Items, Monkeys) ->
+ {" Operation: new = ", OpStr} = lists:split(19, Line),
+ Op = parse_operation_expr(OpStr),
+ parse_test(Rest, Items, Op, Monkeys).
+
+parse_operation_expr("old * old") -> {pow, 2};
+parse_operation_expr("old * " ++ Num) -> {mul, list_to_integer(Num)};
+parse_operation_expr("old + " ++ Num) -> {add, list_to_integer(Num)}.
+
+parse_test([Line | Rest], Items, Op, Monkeys) ->
+ {" Test: divisible by ", Div} = lists:split(21, Line),
+ parse_if_true(Rest, Items, Op, list_to_integer(Div), Monkeys).
+
+parse_if_true([Line | Rest], Items, Op, Div, Monkeys) ->
+ {" If true: throw to monkey ", IfTrue} = lists:split(29, Line),
+ parse_if_false(Rest, Items, Op, Div, list_to_integer(IfTrue), Monkeys).
+
+parse_if_false([Line | Rest], Items, Op, Div, IfTrue, Monkeys) ->
+ {" If false: throw to monkey ", IfFalse} = lists:split(30, Line),
+ Monkey = #monkey{items=Items, op=Op, test=Div, if_true=IfTrue, if_false=list_to_integer(IfFalse)},
+ parse_init(Rest, [Monkey | Monkeys]).
+
+eval_pow(_, 0, _) -> 0;
+eval_pow(X, 1, _) -> X;
+eval_pow(X, N, Xb) -> eval_pow(X * Xb, N - 1, Xb).
+eval_arith({pow, N}, X) -> eval_pow(X, N, X);
+eval_arith({mul, N}, X) -> X * N;
+eval_arith({add, N}, X) -> X + N.
+
+eval_monkey(M = #monkey{items=Items}) ->
+ eval_monkey(Items, [], M).
+eval_monkey([], Thrown, _) ->
+ lists:reverse(Thrown);
+eval_monkey([Item | Rest], Thrown, M) ->
+ Item1 = eval_arith(M#monkey.op, Item) div 3,
+ Target = case Item1 rem M#monkey.test of
+ 0 -> M#monkey.if_true;
+ _ -> M#monkey.if_false
+ end,
+ eval_monkey(Rest, [{Item1, Target} | Thrown], M).
+
+map_states(Monkeys) ->
+ map_states(Monkeys, 0, #{}).
+map_states([], _, States) ->
+ States;
+map_states([Monkey | Rest], Index, Map) ->
+ map_states(Rest, Index + 1, Map#{Index => Monkey#monkey.items}).
+
+map_updates([], Map) ->
+ Map;
+map_updates([{Item, Target} | Rest], Map) ->
+ Items0 = maps:get(Target, Map),
+ Items1 = Items0 ++ [Item],
+ map_updates(Rest, Map#{Target := Items1}).
+
+eval_round(Monkeys, Inspects, EvalMonkey) ->
+ {Count, Items1, Inspects1} = lists:foldl(fun (M, {Index, States, Inspects0}) ->
+ Items = maps:get(Index, States),
+ Inspects1 = Inspects0#{Index => maps:get(Index, Inspects0, 0) + length(Items)},
+ Throws = EvalMonkey(M#monkey{items=Items}),
+ States0 = States#{Index := []},
+ States1 = map_updates(Throws, States0),
+ {Index + 1, States1, Inspects1}
+ end, {0, map_states(Monkeys), Inspects}, Monkeys),
+ Monkeys1 = [M#monkey{items=maps:get(I, Items1)}
+ || {M, I} <- lists:zip(Monkeys, lists:seq(0, Count - 1))],
+ {Monkeys1, Inspects1}.
+
+eval_rounds(N, Monkeys, EvalMonkey) ->
+ eval_rounds(N, Monkeys, EvalMonkey, #{}).
+eval_rounds(0, Monkeys, _, Inspects) ->
+ {Monkeys, Inspects};
+eval_rounds(N, Monkeys, EvalMonkey, Inspects) ->
+ {Monkeys1, Inspects1} = eval_round(Monkeys, Inspects, EvalMonkey),
+ eval_rounds(N - 1, Monkeys1, EvalMonkey, Inspects1).
+
+sol1(Monkeys) ->
+ {_, Inspects} = eval_rounds(20, Monkeys, fun eval_monkey/1),
+ [V1, V2 | _] = lists:reverse(lists:sort(maps:values(Inspects))),
+ V1 * V2.
+
+eval_monkey_2(CommonMod, M = #monkey{items=Items}) ->
+ eval_monkey_2(CommonMod, Items, [], M).
+eval_monkey_2(_, [], Thrown, _) ->
+ lists:reverse(Thrown);
+eval_monkey_2(CommonMod, [Item | Rest], Thrown, M) ->
+ Item1 = eval_arith(M#monkey.op, Item) rem CommonMod,
+ Target = case Item1 rem M#monkey.test of
+ 0 -> M#monkey.if_true;
+ _ -> M#monkey.if_false
+ end,
+ eval_monkey_2(CommonMod, Rest, [{Item1, Target} | Thrown], M).
+
+sol2(Monkeys) ->
+ % Spoiler:
+ % Monkey arithmetic can be treated as-if the item worry values are evaluated
+ % modulo the product of all the test values.
+
+ CommonMod = lists:foldl(fun (M, Prod) -> Prod * M#monkey.test end, 1, Monkeys),
+ Eval = fun (Monkey) -> eval_monkey_2(CommonMod, Monkey) end,
+ {_, Inspects} = eval_rounds(10000, Monkeys, Eval),
+ [V1, V2 | _] = lists:reverse(lists:sort(maps:values(Inspects))),
+ V1 * V2.