Advent of Code 2024 - Day 7

Hello, Erlangers! Ukrainian Erlanger is here! :metal: Day 7 of Advent of Code 2024 is live, and the challenge is waiting for us! :computer: Let’s dive into the problem and show how Erlang’s functional and concurrent capabilities can shine. :rocket:

As always, this thread is a place to share your approaches, solutions, or any hurdles you’re facing. Whether you’ve cracked it or are stuck on an edge case, we’re all here to support and learn together!

:bulb: Tips for Day 7:

  • Carefully read the problem description - details matter more than ever as challenges get more complex.
  • Break the problem into smaller, manageable functions to make debugging easier.
  • Don’t forget to optimize if you find your solution isn’t performing well - Erlang’s efficiency can save the day.

:sparkles: Motivation for today:
No problem is insurmountable with patience and a clear mind. Let’s solve, learn, and BEAM together!

Share your solutions, progress, or just your thoughts about today’s challenge. Let’s keep the momentum going and make Day 7 another memorable day for the Erlang community! :santa:

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

1 Like

I’m actually using a different language this year, but I had to write this in Erlang too to enjoy the ability to [Head|Tail].

-module(day07).

-compile([export_all]).

go() ->
    {ok, Data} = file:read_file("input/day07.txt"),
    Lines = string:lexemes(Data, "\n"),
    Handles = [ rpc:async_call(node(), day07, handle_line, [Line]) || Line <- Lines ],
    lists:foldl(fun
        ({N, both}, {P1, P2}) -> {P1+N, P2+N};
        ({N, p2_only}, {P1, P2}) -> {P1, P2+N};
        (neither, Acc) -> Acc
    end, {0, 0}, [ rpc:yield(Handle) || Handle <- Handles ]).

parse_line(Line) ->
    [TargetS, NumbersS] = string:split(Line, ": "),
    Numbers = [ binary_to_integer(NumberS) || NumberS <- string:split(NumbersS, " ", all) ],
    {binary_to_integer(TargetS), Numbers}.

add(A,B) -> A+B.
mul(A,B) -> A*B.
concat(A,B) -> list_to_integer(io_lib:format("~b~b", [A,B])).

handle_line(Line) ->
    {Target, Numbers} = parse_line(Line),
    case recurse(Target, Numbers, [fun mul/2, fun add/2]) of
        true -> {Target, both};
        false ->
            case recurse(Target, Numbers, [fun concat/2, fun mul/2, fun add/2]) of
                true -> {Target, p2_only};
                false -> neither
            end
    end.

recurse(Target,[OneLeft],_) -> Target == OneLeft;
recurse(Target,[Head|_],_) when Head > Target -> false;
recurse(Target,[A,B|Tail],Ops) ->
    lists:any(fun (Op) ->
        recurse(Target,[Op(A,B)|Tail],Ops)
    end, Ops).

%%% cooler line parsing, not faster (or slower), just more complicated
%
% line_length(<<$\n, Rest/binary>>) -> 0;
% line_length(<<_, Rest/binary>>) -> 1 + line_length(Rest).
% 
% start_jobs(<<>>) -> [];
% start_jobs(Data) ->
%     LineLength = line_length(Data),
%     {Line, <<$\n, Rest/binary>>} = split_binary(Data, LineLength),
%     [rpc:async_call(node(), day07, handle_line, [Line])|start_jobs(Rest)].
% 
% go() ->
%     {ok, Data} = file:read_file("input/day07.txt"),
%     Handles = start_jobs(Data),
%     lists:foldl(fun
%         ({N, both}, {P1, P2}) -> {P1+N, P2+N};
%         ({N, p2_only}, {P1, P2}) -> {P1, P2+N};
%         (neither, Acc) -> Acc
%     end, {0, 0}, [ rpc:yield(Handle) || Handle <- Handles ]).
2 Likes

A fellow freshman is back online.
I don’t know why I keep “parsing” the input myself, instead of split + map, but I guess it makes me more familiar with the language faster.

-module(task_1).

-export([main/1]).
main(File) ->
	{ok, Input} = file:read_file(File),
	Lines = binary:split(Input, <<"\n">>, [global, trim_all]),
	Calibrations = lists:map(fun(Line) -> parse(binary_to_list(Line)) end, Lines),
	lists:foldl(fun({Result, Operands}, Sum) -> Sum + solve(Result, Operands) end, 0, Calibrations).

parse(Line) ->
	parse(Line, [0]).
parse([], Nums) -> 
	[Result|Operands] = lists:reverse(Nums),
	{Result, Operands};
parse([H|T], [NumsH|NumsT] = Nums) -> 
	parse(
	  	T,
		case H of
			$\s -> [0 | Nums];
			Char when Char >= $0, Char =< $9 -> [NumsH*10+(Char-$0) | NumsT];
			_ -> Nums
		end
	).

solve(Res, [H|T]) ->
	case lists:any(fun(Calc) -> Calc == Res end, calc_branches(T, [H])) of
		true -> Res;
		false -> 0
	end.

calc_branches([], Calcs) ->
	Calcs;
calc_branches([H|T], Calcs) ->
	calc_branches(
		T,
		% task 1 and 2 differ only by presence of the third element produced by `concat`
		lists:foldl(fun(Calc, Acc) -> [Calc + H, Calc * H, concat(Calc, H) | Acc] end, [], Calcs)
	).

concat(X, Y) ->
	X * math:pow(10, trunc(math:log10(Y)) + 1) + Y.
1 Like

I found this one particularly fun so I’ll post it :grinning:

1 Like

Love the blend of languages but coming back to Erlang for the joy of [Head|Tail] is always a treat! Your solution is clean and demonstrates the power of recursion and function handling in Erlang. Great use of rpc to distribute the workload too - awesome work! :rocket::christmas_tree:

Great work diving deeper into the language! Parsing the input manually might take a bit longer, but it’s a fantastic way to build familiarity and strengthen your understanding of Erlang. Your solution is well-structured, and I like how you’ve tackled the problem with recursion and manual parsing. Keep it up - your effort is definitely paying off! :rocket::christmas_tree:

Such a fun solution! :tada: I love how clean and modular it is, especially with the use of magmas and clipping to handle constraints elegantly. The structure makes it super readable and adaptable for both parts - great work! :rocket::christmas_tree:

That looks fantastic. I have a lot of stuff to study now. Thanks for sharing your solution kind sire

2 Likes

:pray: I’m now wondering if there is a recursive solution that isn’t O(N*M). I am pretty sure it is O(N * M) anyway. I don’t think I’ll have time to try it because tomorrow is coming FAST. My solution is pretty slow unfortunately but I do love that I am using the list applicative.

1 Like

Here’s my late to the party solution, it’s a bit slow but does the job :smiley:

-module(day7).

-compile(export_all).

main() ->
    main("day7.txt").

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

solve1(Data) ->
    Ops = [fun erlang:'+'/2, 
           fun erlang:'*'/2],
    lists:sum([ V || {V, Ns} <- Data, can_calc(Ops, V, Ns) ]).

solve2(Data) ->
    Ops = [fun erlang:'+'/2, 
           fun erlang:'*'/2, 
           fun concat/2],
    lists:sum([ V || {V, Ns} <- Data, can_calc(Ops, V, Ns) ]).

can_calc(_, V, [A]) -> 
    V == A;
can_calc(Ops, V, [A, B|R]) ->
    lists:any(fun (Op) -> can_calc(Ops, V, [Op(A, B)|R]) end, Ops).

concat(A, B) -> 
    list_to_integer(integer_to_list(A) ++ integer_to_list(B)).
2 Likes

Great solution! :tada: It might be a bit slow, but it’s clean and gets the job done - that’s what matters! I like how you’ve structured the operations and the use of recursion in can_calc. Keep it up, and welcome back to the party! :rocket::christmas_tree: