Advent of Code 2021 - Day 2

This topic is about Day 2 of the Advent of Code 2021.

We have a private leaderboard (shared with users of the elixir forum ):

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

The entry code is:
370884-a6a71927

4 Likes

Thank you @bjorng for introducing me to AoC :slight_smile:

Here’s my solution (omitting file read into list):

solve1(Data) ->
    {X, Y} = lists:foldl(fun (X, Y) -> fold1(X, Y) end, {0, 0}, Data),
    X * Y.

fold1(String, {X, Y}) ->
    [Direction, ValueB] = binary:split(String, <<" ">>),
    Value = binary_to_integer(ValueB),
    case Direction of
        <<"forward">> -> {X + Value, Y};
        <<"down">>    -> {X,         Y + Value};
        <<"up">>      -> {X,         Y - Value}
    end.

solve2(Data) ->
    {X, Y, _} = lists:foldl(fun (X, Y) -> fold2(X, Y) end, {0, 0, 0}, Data),
    X * Y.

fold2(String, {X, Y, Z}) ->
    [Direction, ValueB] = binary:split(String, <<" ">>),
    Value = binary_to_integer(ValueB),
    case Direction of
        <<"forward">> -> {X + Value, Y + (Value * Z), Z};
        <<"down">>    -> {X,         Y,               Z + Value};
        <<"up">>      -> {X,         Y,               Z - Value}
    end.
5 Likes

Code:

-module(day02).
-export([p1/2, p2/2]).

p1([{forward, M} | T], {P, D}) ->
    p1(T, {P + M, D});
p1([{up, M} | T], {P, D}) ->
    p1(T, {P, D - M});
p1([{down, M} | T], {P, D}) ->
    p1(T, {P, D + M});
p1([], {P, D}) ->
    P * D.

p2([{forward, M} | T], {P, D, A}) ->
    p2(T, {P + M, D + A * M, A});
p2([{up, M} | T], {P, D, A}) ->
    p2(T, {P, D, A - M});
p2([{down, M} | T], {P, D, A}) ->
    p2(T, {P, D, A + M});
p2([], {P, D, _A}) ->
    P * D.

Tests:

-module(day02_test).

-include("day02.hrl").
-include_lib("eunit/include/eunit.hrl").

p1_test() ->
    ?assert(day02:p1(test_data(), {0, 0}) =:= 150),
    day02:p1(?INPUT_DATA, {0, 0}).

p2_test() ->
    ?assert(day02:p2(test_data(), {0, 0, 0}) =:= 900),
    day02:p2(?INPUT_DATA, {0, 0, 0}).

test_data() ->
    [
        {forward, 5},
        {down, 5},
        {forward, 8},
        {up, 3},
        {down, 8},
        {forward, 2}
    ].

Edit: I subsequently refactored it to use lists:foldl/3 as I feel that is easier to read for my future self. Only P1 shown for brevity.

p1(InputData, State) ->
    {P, D} = lists:foldl(fun p1_iter/2, State, InputData),
    P * D.

p1_iter({forward, M}, {P, D}) ->
    {P + M, D};
p1_iter({up, M}, {P, D}) ->
    {P, D - M};
p1_iter({down, M}, {P, D}) ->
    {P, D + M}.
4 Likes

My lazier code for day 2 pensandoemelixir/day02_2021.erl at main · adolfont/pensandoemelixir · GitHub

And a command-line version that reads the file from stdin: pensandoemelixir/day02_2021_v2.erl at main · adolfont/pensandoemelixir · GitHub

And a video:

4 Likes

Ok, here is my dirty solution :upside_down_face::

part1() ->
    Input = [{forward, 5}, {down, 5}, {forward, 8}, {up, 3}, {down, 8}, {forward, 2}],
    Output = lists:foldl(fun({X, Y}, Acc) -> Acc#{X => maps:get(X, Acc, 0) + Y} end, #{}, Input),
    #{up := Up, down := Down, forward := Forward} = Output,
    (Down - Up) * Forward. 
part2() ->
    Input = [{forward, 5}, {down, 5}, {forward, 8}, {up, 3}, {down, 8}, {forward, 2}],
    Output = lists:foldl(
      fun ({forward, Y},  Acc = #{forward := Forward, depth := Depth, aim := Aim}) ->
            Acc#{forward => Forward + Y, depth => Y * Aim + Depth, aim => Aim};
          ({down, Y},  Acc = #{down := Down, aim := Aim}) ->
            Acc#{down => Down + Y, aim => Aim + Y};
          ({up, Y},  Acc = #{up := Down, aim := Aim}) ->
            Acc#{down => Down + Y, aim => Aim - Y}
    end, #{up => 0, down => 0, forward => 0, depth => 0, aim => 0}, Input),
    #{forward := Forward, depth := Depth} = Output,
    Forward * Depth.

:metal:

4 Likes

I managed to put two days of Awk solutions in a single tweet:

Makes me wonder how easy it’d be to make an Awk-like construct for Erlang in escript… Might have to prototype that at some point. Would be neat to get something similar with parse transforms.

9 Likes

The first part is pretty messy and I am not going to share it here. However I am really proud of the second part:

partTwo([[<<"forward">>, Y] | Z], {H, D, A}) -> partTwo(Z, {H + binary_to_integer(Y), D + A * binary_to_integer(Y), A});
partTwo([[<<"up">>, Y] | Z], {H, D, A}) -> partTwo(Z, {H, D, A - binary_to_integer(Y)});
partTwo([[<<"down">>, Y] | Z], {H, D, A}) -> partTwo(Z, {H, D, A + binary_to_integer(Y)});
partTwo([], {H, D, _A}) -> H * D.

I am getting all of the input from a .txt file in the same directory so that is why I am using binary strings.

4 Likes

Day 2 in Sesterl adventofcode/main.sest at master · michallepicki/adventofcode · GitHub

5 Likes

Very OCaml-like there!

5 Likes

My take on day 2…

-module(day2).
-export([part1/0, part2/0]).

-record(state, {x = 0, depth = 0}).
-record(state2, {x = 0, depth = 0, aim = 0}).

part1() ->
    #state{x = X, depth = Depth} = lists:foldl(fun(Command, Acc) -> 
        apply_command(Command, Acc) 
    end, #state{}, load_commands()),
    X * Depth.
    
part2() ->
    #state2{x = X, depth = Depth} = lists:foldl(fun(Command, Acc) -> 
        apply_command(Command, Acc) 
    end, #state2{}, load_commands()),
    X * Depth.

% For state
apply_command({forward, X}, #state{x = PrevX} = State) ->
    State#state{x = PrevX + X};
apply_command({up, Depth}, #state{depth = PrevDepth} = State) ->
    State#state{depth = PrevDepth - Depth};
apply_command({down, Depth}, #state{depth = PrevDepth} = State) ->
    State#state{depth = PrevDepth + Depth};
% For state2
apply_command({forward, X}, #state2{x = PrevX, depth = PrevDepth, aim = PrevAim} = State) ->
    State#state2{x = PrevX + X, depth = PrevDepth + (PrevAim * X)};
apply_command({up, Aim}, #state2{aim = PrevAim} = State) ->
    State#state2{aim = PrevAim - Aim};
apply_command({down, Aim}, #state2{aim = PrevAim} = State) ->
    State#state2{aim = PrevAim + Aim}.

load_commands() ->
    {ok, Binary} = file:read_file("input.txt"),
    List = binary:split(Binary, <<"\n">>, [global, trim]),
    lists:map(fun(X) -> to_command(X) end, List).

to_command(X) when is_binary(X) ->
    to_command(binary:split(X, <<" ">>));
to_command([Key, Value]) ->
    case Key of
        <<"forward">> -> {forward, to_int(Value)};
        <<"up">> -> {up, to_int(Value)};
        <<"down">> -> {down, to_int(Value)}
    end.

to_int(Binary) ->
    list_to_integer(binary_to_list(Binary)).

It was new for me to replace Elixir structs with Erlang records.

And also, I did not find an equivalent to Elixir nested modules :slight_smile:

defmodule Day2 do
  defmodule State do
    ...
  end
  defmodule State2 do
    ...
  end
  ...
end
4 Likes

Indeed, nested modules do not exist, as the Erlang namespace is flat, and one module = one .erl file. What is solved by namespaces in other languages is normally solved by convention in Erlang. http_server vs http_client vs tcp_client and so on rather than HTTP.Client, HTTP.Server and TCP.Client.

I appreciate the flat namespace more and more each time I have to fight with all the infernal combinations of go version, GOPATH and go.mod, a chaotic system carefully engineered to ensure you will fail and you will increase Google traffic.

3 Likes