# HG changeset patch # User paulsnar # Date 1670769940 -7200 # Sun Dec 11 16:45:40 2022 +0200 # Node ID 0227781dea04c42708abf72815796e6e0e848d65 # Parent 915076c571ad8ea7b83ae3e553da7a6b95edfa82 11 diff --git a/11/input.txt b/11/input.txt new file mode 100644 --- /dev/null +++ b/11/input.txt @@ -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 diff --git a/11/sol.erl b/11/sol.erl new file mode 100644 --- /dev/null +++ b/11/sol.erl @@ -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.