Advent of Code 2024 - Day 5

Hello, Erlangers! Ukrainian Erlanger is here! :metal: Day 5 has arrived, and it’s time for another thrilling challenge! :computer: Let’s show how functional programming and the power of Erlang can conquer today’s puzzle. :rocket:

Whether you’re testing ideas in the shell or crafting your final solution, every step counts. Share your strategies, solutions, or even the struggles - we’re all in this together!

:bulb: Tips for Day 5:

  • Use the shell for quick experiments - it’s perfect for testing your logic.
  • Break down the problem into small, manageable functions - Erlang shines with modular solutions.
  • Keep it simple - clear solutions are often the most elegant.

:sparkles: Motivation for today:
Each challenge is a chance to grow. Solve boldly, learn deeply, and let’s Beam together!

Drop your thoughts, progress, and solutions below. Let’s make Day 5 another amazing chapter for the Erlang community! :santa:

Happy coding, and may your recursion always terminate! :christmas_tree::computer:

2 Likes

Here’s my solution:

-module(day5).

-compile(export_all).

main(File) ->
    {ok, RawData} = file:read_file(File),
    [RawRules, RawUpdates] = binary:split(RawData, <<"\n\n">>, [global, trim]),
    Rules =
        [begin
             [X, Y] = binary:split(Line, <<"|">>, [global]),
             {binary_to_integer(X), binary_to_integer(Y)}
         end
         || Line <- binary:split(RawRules, <<"\n">>, [global, trim])],
    Updates =
        [[binary_to_integer(X) || X <- binary:split(Line, <<",">>, [global])]
         || Line <- binary:split(RawUpdates, <<"\n">>, [global, trim])],
    io:format("part 1: ~p~n", [solve1(Rules, Updates)]),
    io:format("part 2: ~p~n", [solve2(Rules, Updates)]).

solve1(Rules, Updates) ->
    Set = sets:from_list(Rules, [{version, 2}]),
    lists:sum([get_middle(U) || U <- Updates, is_valid_update(U, Set)]).

get_middle(List) ->
    lists:nth(round(length(List) / 2), List).

is_valid_update([H | T], Set) ->
    is_valid_update(T, [H], Set).

is_valid_update([], _, _) -> true;
is_valid_update([H | T], Seen, Set) ->
    ToCheck = [{H, X} || X <- Seen],
    case lists:any(fun(R) -> sets:is_element(R, Set) end, ToCheck) of
        true  -> false;
        false -> is_valid_update(T, [H | Seen], Set)
    end.

solve2(Rules, Updates) ->
    Set = sets:from_list(Rules, [{version, 2}]),
    lists:sum([get_middle(fix_update(U, Set)) || U <- Updates, not is_valid_update(U, Set)]).

fix_update([H | T], Set) ->
    fix_update(T, [H], Set).

fix_update([],    Seen, _Set) -> lists:reverse(Seen);
fix_update([H|T], Seen,  Set) ->
    case lists:search(fun (X) -> sets:is_element({H, X}, Set) end, Seen) of
        false      -> fix_update(T, [H | Seen], Set);
        {value, X} -> fix_update(lists:reverse(replace(X, H, Seen)) ++ [X|T], Set)
    end.

replace(X, Y, List) ->
    replace(X, Y, List, []).

replace(X, Y, [H|T], Acc) when H =:= X ->
    lists:reverse(Acc) ++ [Y | T];
replace(X, Y, [H|T], Acc) ->
    replace(X, Y, T, [H | Acc]).

Apparently in fix_update - lists:search/2 and replace/3 are unnecessary as rule violation will always happen between two neighboring elements, but I kept it as.

2 Likes

Great solution! I love how cleanly you’ve structured everything, especially the use of sets for rule validation and your handling of updates. Even with the extra steps in fix_update, it’s a solid and readable approach. Nicely done! :rocket::christmas_tree:

1 Like