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
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
Thank you @bjorng for introducing me to AoC
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.
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}.
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:
Ok, here is my dirty solution :
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.
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.
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.
Very OCaml-like there!
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
defmodule Day2 do
defmodule State do
...
end
defmodule State2 do
...
end
...
end
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.