Advent of Code 2024 - Day 1

Hi folks! Ukrainian Erlanger is here :metal:! The wait is almost over! Advent of Code 2024 is just around the corner, and Day 1 will unlock in a matter of hours. It’s time to sharpen your tools, warm up your Erlang shells, and prepare for another year of coding challenges!

No hints, no spoilers - just anticipation and the promise of puzzles that will test our problem-solving skills and showcase Erlang’s elegance.

:bulb: Pro Tips Before the Countdown Ends:
• Set up your environment - whether it’s your favorite editor or the trusty Erlang shell.
• Have a hot drink and snacks ready; debugging is better with a warm cup by your side! :coffee:
• Remember: simplicity and recursion are your best friends.

:sparkles: Motivation:
The hardest part of any journey is the first step. Let’s make that step in Erlang - one function, one match, one challenge at a time.

Check back here when Day 1 goes live to share your first impressions and start the conversation. Let’s solve this together, Erlangers!

Happy coding, and see you at the starting line! :santa:

7 Likes

:christmas_tree: Advent of Code 2024 - Day 1: My Solution :christmas_tree:

Hey everyone! :wave:

Day 1 is finally here, and I just finished working on my solution in Erlang. :rocket: It was a fun challenge involving parsing input data and transforming it into usable structures. I focused on splitting the input into two separate lists, one for each column, with all items converted to integers.

It’s always satisfying to see Erlang’s pattern matching and list comprehensions shine in tasks like this. :bulb:

How’s everyone else doing? Share your approaches, tips, or even struggles - let’s learn from each other and make this Advent of Code journey memorable! :santa:

Happy coding, and let’s Beam together! :sparkles:

-module(day_1).

-export([task_1/0, task_2/0]).

task_1() ->
    File = "input",
    {ok, RawData} = file:read_file(File),
    Raw = binary:split(RawData, <<"\n">>, [global, trim]),
    Data = [[binary_to_integer(X) || X <- binary:split(R, <<" ">>, [global, trim]), X /= <<>>] || R <- Raw],
    sum(prepare(Data, [], []), 0).

prepare([], Left, Right) ->
    [lists:sort(Left), lists:sort(Right)];
prepare([[L,  R] | Rest], Left, Right) ->
    prepare(Rest, [L | Left], [R | Right]).

sum([[], []], Sum) ->
    Sum;
sum([[L | T1], [R | T2]], Sum) when L >= R ->
    sum([T1, T2], (Sum + (L - R)));
sum([[L | T1], [R | T2]], Sum) when R >= L ->
    sum([T1, T2], (Sum + (R - L))).

task_2() ->
    File = "input2",
    {ok, RawData} = file:read_file(File),
    Raw = binary:split(RawData, <<"\n">>, [global, trim]),
    Data = [[binary_to_integer(X) || X <- binary:split(R, <<" ">>, [global, trim]), X /= <<>>] || R <- Raw],
    sum2(prepare2(Data, [], []), 0).

prepare2([], Left, Right) ->
    [Left, Right];
prepare2([[L,  R] | Rest], Left, Right) ->
    prepare2(Rest, [L | Left], [R | Right]).

sum2([[], _], Sum) ->
    Sum;
sum2([[L | Rest], Right], Sum) ->
    sum2([Rest, Right], Sum + (L * erlang:length([L || R <- Right, R == L]))).
4 Likes

Thank you @vkatsuba for creating AoC threads again this year :slight_smile:

Here’s my Day 1 solution:

-module(day1).

-compile(export_all).

main(File) ->
    {ok, RawData} = file:read_file(File),
    Data = 
        [begin
             [A, B] = binary:split(L, <<"   ">>),
             {binary_to_integer(A), binary_to_integer(B)}
         end
         || L <- binary:split(RawData, <<"\n">>, [global, trim])],
    io:format("part 1: ~p~n", [solve1(Data)]),
    io:format("part 2: ~p~n", [solve2(Data)]).

solve1(Data) ->
    {A, B} = lists:unzip(Data),
    lists:sum([ abs(L - R) || {L, R} <- lists:zip(lists:sort(A), lists:sort(B)) ]).

solve2(Data) ->
    {A, B} = lists:unzip(Data),
    Counts = lists:foldl(fun(N, Acc) -> maps:update_with(N, fun(V) -> V + 1 end, 1, Acc) end, #{}, B),
    lists:foldl(fun(N, Acc) -> N * maps:get(N, Counts, 0) + Acc end, 0, A).

4 Likes

You’re welcome! :christmas_tree: Great to see your solution for Day 1 - awesome work! :computer: Looking forward to seeing more creative approaches from everyone this year. Let’s keep the AoC spirit alive! :rocket:

3 Likes

Here is my solution:

#!/usr/bin/env escript

main(_) ->
    Input = get_input(),
    io:format("Part 1: ~p\n", [part1(Input)]),
    io:format("Part 2: ~p\n", [part2(Input)]).

part1(Input0) ->
    {L1,L2} = lists:unzip(Input0),
    lists:sum(lists:zipwith(fun(A, B) ->
                                    abs(A - B)
                            end,
                            lists:sort(L1),
                            lists:sort(L2))).
    %% In OTP 28 the preceding expression can be replaced with:
    %% lists:sum([abs(A - B) ||
    %%               A <- lists:sort(L1) && B <- lists:sort(L2)]).

part2(Input0) ->
    {L1,L2} = lists:unzip(Input0),
    Gs = maps:groups_from_list(fun(I) -> I end, L2),
    lists:sum([A * length(maps:get(A, Gs, [])) || A <- L1]).

get_input() ->
    {ok,Input} = file:read_file("input.txt"),
    Lines = binary:split(Input, ~"\n", [global,trim]),
    [begin
         [A,B] = binary:split(Line, ~" ", [global,trim_all]),
         {binary_to_integer(A),
          binary_to_integer(B)}
     end || Line <- Lines].

I also have a solution in Elixir.

4 Likes

Your solution looks fantastic! I love seeing the different ways we can approach these challenges in Erlang. Great work on Day 1 - excited to see what you come up with next! :christmas_tree::rocket:

3 Likes

I’ve implemented and seen many implementations of Enum.frequencies in production code, do you think it’s a good idea to add something similar to OTP maps or lists module?

2 Likes

Yes, I think that an implementation of Enum.frequencies could be a useful addition to the maps module.

3 Likes

Late to the party, as usual.

day01.erl:

-module(day01).
-include_lib("eunit/include/eunit.hrl").

-export([p1/1, p2/1]).

p1(InputData) ->
    [As, Bs] = transpose_input(InputData),
    lists:sum([abs(A - B) || {A, B} <- lists:zip(lists:sort(As), lists:sort(Bs))]).

p2(InputData) ->
    [As, Bs] = transpose_input(InputData),
    lists:sum([A * length([B || B <- Bs, A =:= B]) || A <- As]).

transpose_input(InputData) ->
    util:transpose([util:str_to_ints(Row) || Row <- InputData]).

gen_test_() ->
    {setup, fun read_input/0, fun check_results/1}.

read_input() ->
    {
        util:read_input(?MODULE_STRING ++ "_test.txt"),
        util:read_input(?MODULE_STRING ++ "_input.txt")
    }.

check_results({TestData, InputData}) ->
    [
        ?_assertEqual(11, p1(TestData)),
        ?_assertEqual(31, p2(TestData)),
        ?_assertEqual(2192892, p1(InputData)),
        ?_assertEqual(22962826, p2(InputData))
    ].

util.erl:

-module(util).

-export([read_input/1, str_to_ints/1, transpose/1]).

-spec read_input(Name) -> Lines when
    Name :: string(),
    Lines :: [string()].

read_input(Name) ->
    File = filename:join([code:priv_dir(escratch), Name]),
    {ok, S} = file:open(File, [read]),
    read_lines(S, []).

read_lines(S, Lines) ->
    case file:read_line(S) of
        {ok, Line} ->
            read_lines(S, [string:trim(Line) | Lines]);
        eof ->
            file:close(S),
            lists:reverse(Lines)
    end.

-spec str_to_ints(S) -> Ints when
    S :: string(),
    Ints :: [integer()].

str_to_ints(S) ->
    [list_to_integer(X) || X <- string:tokens(S, " ")].

-spec transpose(Matrix1) -> Matrix2 when
    Matrix1 :: [nonempty_list()],
    Matrix2 :: [nonempty_list()].

transpose([[] | _]) -> [];
transpose(M) -> [lists:map(fun hd/1, M) | transpose(lists:map(fun tl/1, M))].
2 Likes

Better late than never! :rocket: I really like your approach - using transpose_input to structure the data cleanly and leveraging lists:zip for pairwise operations makes the solution both readable and efficient. Great use of EUnit for testing as well - those assertions ensure everything is solid. Nice work! :christmas_tree::computer:

1 Like

I spent a lot more time on prettifying than on solving :wink:

1 Like

Sometimes prettifying takes as much creativity as solving! :wink: A clean and elegant solution is always satisfying.

1 Like