2 files changed, 179 insertions(+), 0 deletions(-)

A => 11/input.txt
A => 11/sol.erl
A => 11/input.txt +55 -0
@@ 0,0 1,55 @@ 
+Monkey 0:
+  Starting items: 57, 58
+  Operation: new = old * 19
+  Test: divisible by 7
+    If true: throw to monkey 2
+    If false: throw to monkey 3
+
+Monkey 1:
+  Starting items: 66, 52, 59, 79, 94, 73
+  Operation: new = old + 1
+  Test: divisible by 19
+    If true: throw to monkey 4
+    If false: throw to monkey 6
+
+Monkey 2:
+  Starting items: 80
+  Operation: new = old + 6
+  Test: divisible by 5
+    If true: throw to monkey 7
+    If false: throw to monkey 5
+
+Monkey 3:
+  Starting items: 82, 81, 68, 66, 71, 83, 75, 97
+  Operation: new = old + 5
+  Test: divisible by 11
+    If true: throw to monkey 5
+    If false: throw to monkey 2
+
+Monkey 4:
+  Starting items: 55, 52, 67, 70, 69, 94, 90
+  Operation: new = old * old
+  Test: divisible by 17
+    If true: throw to monkey 0
+    If false: throw to monkey 3
+
+Monkey 5:
+  Starting items: 69, 85, 89, 91
+  Operation: new = old + 7
+  Test: divisible by 13
+    If true: throw to monkey 1
+    If false: throw to monkey 7
+
+Monkey 6:
+  Starting items: 75, 53, 73, 52, 75
+  Operation: new = old * 7
+  Test: divisible by 2
+    If true: throw to monkey 0
+    If false: throw to monkey 4
+
+Monkey 7:
+  Starting items: 94, 60, 79
+  Operation: new = old + 2
+  Test: divisible by 3
+    If true: throw to monkey 1
+    If false: throw to monkey 6

          
A => 11/sol.erl +124 -0
@@ 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.