Advent of Code Day 11 - Discussion

This topic is about Day 11 of the Advent of Code 2022 .

Sorry for delaying this post. I bet folks already solved it, let’s see how. I’ll start my attempt in a bit.

Link to our leaderboard:

https://adventofcode.com/2022/leaderboard/private/view/370884

The entry code is:
370884-a6a71927

Good luck!

2 Likes

I will make an attempt now. I am thinking gen_server and simulate the throws and catch. I am hoping catch and throw are allowable terms in Erlang.

1 Like

I had to scratch that idea. I initially though monkeys will be concurrently throwing and catching but I was wrong. It was a lot simpler than that. Although I had to go for hints for Part 2.

Here’s how I did this:

-module(day_11).

-export([solve/0]).

input() ->
  element(2, file:read_file("data/11.txt")).

solve() ->
  MonkeyInfo = parse(input()),
  {solve1(MonkeyInfo), solve2(MonkeyInfo)}.

solve1(MonkeyInfo) ->
  top_2(rounds(MonkeyInfo, 20, 3, 'div')).

solve2(MonkeyInfo) ->
  RelaxFactor =
    lists:foldl(fun(X, Acc) -> X * Acc end,
                1,
                [maps:get(divisible, X) || {_, X} <- maps:to_list(MonkeyInfo)]),
  top_2(rounds(MonkeyInfo, 10_000, RelaxFactor, 'rem')).

rounds(MonkeyInfo, Count, RelaxFactor, Relaxor) ->
  lists:foldl(fun(_, Acc) -> single_round(Acc, RelaxFactor, Relaxor) end,
              MonkeyInfo,
              lists:seq(1, Count)).

top_2(Rounds) ->
  [{_, #{inspected := A}}, {_, #{inspected := B}} | _] =
    lists:sort(fun({_, X}, {_, Y}) -> maps:get(inspected, X) > maps:get(inspected, Y) end,
               maps:to_list(Rounds)),
  A * B.

single_round(MonkeyInfo, RelaxFactor, Relaxor) ->
  Monkeys = lists:seq(0, map_size(MonkeyInfo) - 1),
  UpdateMonkeyInfo =
    fun(WorryLevel, Target, Source, Map) ->
       maps:update_with(Target,
                        fun(X) -> maps:update_with(items, fun(I) -> I ++ [WorryLevel] end, X) end,
                        maps:update_with(Source,
                                         fun(X) ->
                                            X#{items := [], inspected := maps:get(inspected, X) + 1}
                                         end,
                                         Map))
    end,
  Reducer =
    fun(Idx, AccMonkey) ->
       #{items := Items,
         op := Op,
         divisible := Divisible,
         throw1 := Throw1,
         throw2 := Throw2} =
         maps:get(Idx, AccMonkey),
       UpdatedWorryLevel = [Op(I) || I <- Items],
       AfterRelax = [apply(erlang, Relaxor, [W, RelaxFactor]) || W <- UpdatedWorryLevel],
       lists:foldl(fun(X, Acc) ->
                      case X rem Divisible of
                        0 -> UpdateMonkeyInfo(X, Throw1, Idx, Acc);
                        _ -> UpdateMonkeyInfo(X, Throw2, Idx, Acc)
                      end
                   end,
                   AccMonkey,
                   AfterRelax)
    end,
  lists:foldl(Reducer, MonkeyInfo, Monkeys).

parse(Input) ->
  RawMonkeyInfo = binary:split(Input, <<"\n\n">>, [global]),
  maps:from_list([parse_monkey(X) || X <- RawMonkeyInfo]).

parse_monkey(MonkeyRow) ->
  [<<"Monkey ", Id:8, ":">>,
   <<"  Starting items: ", Items/binary>>,
   <<"  Operation: new = old ", Op/binary>>,
   <<"  Test: divisible by ", Divisible/binary>>,
   <<"    If true: throw to monkey ", Throw1/binary>>,
   <<"    If false: throw to monkey ", Throw2/binary>>] =
    binary:split(MonkeyRow, <<"\n">>, [global]),
  {Id - $0,
   #{items =>
       lists:map(fun(X) -> binary_to_integer(X) end, binary:split(Items, <<", ">>, [global])),
     inspected => 0,
     op => parse_operator(Op),
     divisible => binary_to_integer(Divisible),
     throw1 => binary_to_integer(Throw1),
     throw2 => binary_to_integer(Throw2)}}.

parse_operator(Op) ->
  [X, Operand] = binary:split(Op, <<" ">>),
  Operator = binary_to_atom(X),
  case Operand of
    <<"old">> ->
      fun(Old) -> apply(erlang, Operator, [Old, Old]) end;
    C ->
      fun(Old) -> apply(erlang, Operator, [Old, binary_to_integer(C)]) end
  end.
2 Likes

State of my code was a complete mess as I solved the challenge, and now I tidied it up a bit so it’s not as painful to read:

main(File) ->
    {ok, RawData} = file:read_file(File),
    Data = maps:from_list([ parse_monkey(Block) || Block <- binary:split(RawData, <<"\n\n">>, [global, trim]) ]),
    io:format("part 1: ~p~n", [solve1(Data)]),
    io:format("part 2: ~p~n", [solve2(Data)]).

parse_monkey(Block) ->
    [N1, N2, N3, N4, N5, N6] = binary:split(Block, <<"\n">>, [global, trim]),
    [Id] = get_integers(N1),
    {Id, #{items   => get_integers(N2),
           op      => parse_operation(N3),
           test    => hd(get_integers(N4)),
           targets => get_integers(<<N5/binary, N6/binary>>),
           count   => 0}}.

get_integers(Bin) ->
    {match, Match} = re:run(Bin, <<"([-\\d]+)">>, [{capture, all_but_first, binary}, global]),
    [ binary_to_integer(X) || [X] <- Match ].

parse_operation(Bin) ->
    [_, Operation0] = binary:split(Bin, <<"new = old ">>, [global, trim]),
    <<Op:1/binary, " ", Arg/binary>> = Operation0,
    Fun = case Op of
              <<"*">> -> fun erlang:'*'/2;
              <<"+">> -> fun erlang:'+'/2
          end,
    case Arg of
        <<"old">> -> fun (X) -> Fun(X, X) end;
        N0        -> fun (X) -> Fun(X, binary_to_integer(N0)) end
    end.

solve1(Data) ->
    simulate(Data, 3, 20).

solve2(Data) ->
    simulate(Data, 1, 10000).

simulate(Map, DivideBy, Iterations) ->
    NewMap   = lists:foldl(fun (_, X) -> simulate(X, DivideBy) end, Map, lists:seq(1, Iterations)),
    [A, B|_] = lists:reverse(lists:sort([ X || #{count := X} <- maps:values(NewMap) ])),
    A * B.

simulate(Map, DivideBy) ->
    LCD = lists:foldl(fun erlang:'*'/2, 1, [ X || #{test := X} <- maps:values(Map) ]),
    lists:foldl(fun (Id, Acc) -> simulate(Id, DivideBy, LCD, Acc) end, Map, maps:keys(Map)).

simulate(Id, DivideBy, LCD, Map) ->
    #{items := Items,
      count := Count} = Monkey = maps:get(Id, Map),
    NewMap = Map#{Id => Monkey#{items => [],
                                count => Count + length(Items)}},
    lists:foldl(fun (I, Acc) -> examine(I, DivideBy, LCD, Monkey, Acc) end, NewMap, Items).

examine(N, DivideBy, LCD, Monkey, Acc) ->
    #{op      := Op,
      test    := Test,
      targets := [IfTrue, IfFalse]} = Monkey,
    NewN    = Op(N) div DivideBy rem LCD,
    ThrowTo = case NewN rem Test of
                  0 -> IfTrue;
                  _ -> IfFalse
              end,
    maps:update_with(ThrowTo, fun(M = #{items := Items}) -> M#{items => Items ++ [NewN]} end, Acc).
1 Like