Advent of Code 2024 - Day 4

Hey folks, Ukrainian Erlanger is here :metal:! It’s Day 4, and the challenges keep rolling in! :computer: Today’s puzzle is here to test our problem-solving skills, and what better way to tackle it than with the power of Erlang? :rocket:

As always, let’s focus on breaking down the problem, writing clean and efficient code, and sharing insights along the way. Whether you’re a seasoned Erlanger or just getting started, every solution is a chance to learn and grow!

:bulb: Pro Tips for Day 4:

  • Think about how recursion and pattern matching can simplify complex problems.
  • Explore the lists and maps modules - they’re packed with useful tools.
  • Don’t hesitate to ask questions or share partial solutions - collaboration is key!

:sparkles: Motivation for today:
Each challenge is a stepping stone to mastery. Keep coding, keep learning, and let’s Beam together!

Post your solutions, strategies, or even the hurdles you’ve faced. Let’s solve Day 4 as a community and celebrate the journey!

Happy coding, and may your functions always return the right values! :santa:

2 Likes

Here’s my solution after some cleanup:

-module(day4).

-compile(export_all).

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

load(Data) ->
    maps:from_list([{{X, Y}, N}
                    || {Y, Line} <- lists:enumerate(Data), 
                       {X, N}    <- lists:enumerate(Line)]).

solve1(Map) ->
    Cs = [[{0, 0}, { 0, 1}, { 0, 2}, { 0, 3}],
          [{0, 0}, { 1, 0}, { 2, 0}, { 3, 0}],
          [{0, 0}, { 1, 1}, { 2, 2}, { 3, 3}],
          [{0, 0}, {-1, 1}, {-2, 2}, {-3, 3}]],
    lists:sum([1 || XY <- maps:keys(Map), A <- Cs, is_xmas(XY, A, Map)]).

is_xmas(XY, Cs, Map) ->
    Vs = get_coords(XY, Cs, Map),
    Vs =:= "XMAS" orelse Vs =:= "SAMX".

get_coords({X, Y}, Cs, Map) ->
    [maps:get({X + Xc, Y + Yc}, Map, undefined) || {Xc, Yc} <- Cs].

solve2(Map) ->
    D1 = [{-1, -1}, {0, 0}, {1,  1}],
    D2 = [{-1,  1}, {0, 0}, {1, -1}],
    lists:sum([1 || XY <- maps:keys(Map), is_mas(XY, D1, Map) andalso is_mas(XY, D2, Map)]).

is_mas(XY, Cs, Map) ->
    Vs = get_coords(XY, Cs, Map),
    Vs =:= "MAS" orelse Vs =:= "SAM".
3 Likes

Here’s my odd but working solution! :computer: It might not be the prettiest, but it gets the job done. Sometimes, functionality comes first! :christmas_tree::rocket:

-module(day_4).

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

task_1() ->
    File = "input",
    {ok, RawData} = file:read_file(File),
    Raw = binary:split(RawData, <<"\n">>, [global, trim]),
    horizontal_sum(Raw, 0) + vertical_sum(Raw, 0) + diagonal_sum_top(Raw, 0) + diagonal_sum_bottom(Raw, 0).

horizontal_sum([], Sum) ->
    Sum;
horizontal_sum([H | T], Sum) ->
    Left = horizontal_xmas(H, 0),
    Right = horizontal_xmas(bin_revert(H, <<>>), 0),
    horizontal_sum(T, Sum + Left + Right).

bin_revert(<<>>, Acc) ->
    Acc;
bin_revert(<<Char:1/binary, Rest/binary>>, Acc) ->
    bin_revert(Rest, <<Char/binary, Acc/binary>>).

horizontal_xmas(<<>>, Sum) ->
    Sum;
horizontal_xmas(<<"XMAS", Rest/binary>>, Sum) ->
    horizontal_xmas(Rest, Sum + 1);
horizontal_xmas(<<_, Rest/binary>>, Sum) ->
    horizontal_xmas(Rest, Sum).

vertical_sum([], Sum) ->
    Sum;
vertical_sum([H1, H2, H3, H4 | T], Sum) ->
    vertical_sum([H2, H3, H4 | T], vertical_sum(H1, H2, H3, H4, Sum));
vertical_sum([_ | T], Sum) ->
    vertical_sum(T, Sum).

vertical_sum(<<>>, <<>>, <<>>, <<>>, Sum) ->
    Sum;
vertical_sum(<<"X", R1/binary>>, <<"M", R2/binary>>, <<"A", R3/binary>>, <<"S", R4/binary>>, Sum) ->
    vertical_sum(R1, R2, R3, R4, Sum + 1);
vertical_sum(<<"S", R1/binary>>, <<"A", R2/binary>>, <<"M", R3/binary>>, <<"X", R4/binary>>, Sum) ->
    vertical_sum(R1, R2, R3, R4, Sum + 1);
vertical_sum(<<_, R1/binary>>, <<_, R2/binary>>, <<_, R3/binary>>, <<_, R4/binary>>, Sum) ->
    vertical_sum(R1, R2, R3, R4, Sum).

diagonal_sum_top([], Sum) ->
    Sum;
diagonal_sum_top([H1, H2, H3, H4 | T], Sum) ->
    NewSum = process_columns(H1, H2, H3, H4, 0, byte_size(H1), 0),
    diagonal_sum_top([H2, H3, H4 | T], Sum + NewSum);
diagonal_sum_top([_ | T], Sum) ->
    diagonal_sum_top(T, Sum).

process_columns(_, _, _, _, Col, MaxCol, Sum) when Col > MaxCol - 4 ->
    Sum;
process_columns(Row1, Row2, Row3, Row4, Col, MaxCol, Sum) ->
    Char1 = binary:part(Row1, Col, 1),
    Char2 = binary:part(Row2, Col + 1, 1),
    Char3 = binary:part(Row3, Col + 2, 1),
    Char4 = binary:part(Row4, Col + 3, 1),
    case {Char1, Char2, Char3, Char4} of
        {<<"X">>, <<"M">>, <<"A">>, <<"S">>} ->
            process_columns(Row1, Row2, Row3, Row4, Col + 1, MaxCol, Sum + 1);
        {<<"S">>, <<"A">>, <<"M">>, <<"X">>} ->
            process_columns(Row1, Row2, Row3, Row4, Col + 1, MaxCol, Sum + 1);
        _ ->
            process_columns(Row1, Row2, Row3, Row4, Col + 1, MaxCol, Sum)
    end.

diagonal_sum_bottom([], Sum) ->
    Sum;
diagonal_sum_bottom([H1, H2, H3, H4 | T], Sum) ->
    NewSum = process_columns_bottom(H1, H2, H3, H4, 0, byte_size(H1), 0),
    diagonal_sum_bottom([H2, H3, H4 | T], Sum + NewSum);
diagonal_sum_bottom([_ | T], Sum) ->
    diagonal_sum_bottom(T, Sum).

process_columns_bottom(_, _, _, _, Col, MaxCol, Sum) when Col > MaxCol - 4 ->
    Sum;

process_columns_bottom(Row1, Row2, Row3, Row4, Col, MaxCol, Sum) ->
    Char1 = binary:part(Row4, Col, 1),
    Char2 = binary:part(Row3, Col + 1, 1),
    Char3 = binary:part(Row2, Col + 2, 1),
    Char4 = binary:part(Row1, Col + 3, 1),
    case {Char1, Char2, Char3, Char4} of
        {<<"X">>, <<"M">>, <<"A">>, <<"S">>} ->
            process_columns_bottom(Row1, Row2, Row3, Row4, Col + 1, MaxCol, Sum + 1);
        {<<"S">>, <<"A">>, <<"M">>, <<"X">>} ->
            process_columns_bottom(Row1, Row2, Row3, Row4, Col + 1, MaxCol, Sum + 1);
        _ ->
            process_columns_bottom(Row1, Row2, Row3, Row4, Col + 1, MaxCol, Sum)
    end.

task_2() ->
    File = "input2",
    {ok, RawData} = file:read_file(File),
    Raw = binary:split(RawData, <<"\n">>, [global, trim]),
    x_mas_sum(Raw, 0).

x_mas_sum([], Sum) ->
    Sum;
x_mas_sum([H1, H2, H3 | T], Sum) ->
    NewSum = x_mas_check(H1, H2, H3, 0, byte_size(H1)),
    x_mas_sum([H2, H3 | T], Sum + NewSum);
x_mas_sum([_ | T], Sum) ->
    x_mas_sum(T, Sum).

x_mas_check(_, _, _, Col, MaxCol) when Col > MaxCol - 3 ->
    0;
x_mas_check(Row1, Row2, Row3, Col, MaxCol) ->
    TopLeft = binary:part(Row1, Col, 1),
    Center = binary:part(Row2, Col + 1, 1),
    BottomLeft = binary:part(Row3, Col, 1),
    BottomRight = binary:part(Row3, Col + 2, 1),
    TopRight = binary:part(Row1, Col + 2, 1),
    Match = case {TopLeft, Center, BottomLeft, BottomRight, TopRight} of
        {<<"M">>, <<"A">>, <<"M">>, <<"S">>, <<"S">>} -> 1;
        {<<"S">>, <<"A">>, <<"S">>, <<"M">>, <<"M">>} -> 1;
        {<<"S">>, <<"A">>, <<"M">>, <<"M">>, <<"S">>} -> 1;
        {<<"M">>, <<"A">>, <<"S">>, <<"S">>, <<"M">>} -> 1;
        _ ->
            0
    end,
    Match + x_mas_check(Row1, Row2, Row3, Col + 1, MaxCol).
1 Like

Wow, I love it! :heart_eyes: Your solution is so compact and clear - really impressive work! :rocket: Keep going, you’re doing great! :christmas_tree::computer:

1 Like

Jeez. That is gorgeous.

2 Likes

I am an Erlang freshman, so I did not utilize pretty much anything.
But here it is.

-module(task_1).

-export([main/1]).

main(File) ->
	{ok, Input} = file:read_file(File),
	LineLen = line_len(Input),
	Data = binary:replace(Input, <<$\n>>, <<>>, [global]),

	count_xmas(Data, LineLen).

line_len(Data) ->
	line_len(Data, 0).
line_len(Data, Idx) when Idx >= byte_size(Data) ->
	0;
line_len(Data, Idx) ->
	case binary:at(Data, Idx) of
		$\n -> Idx;
		_ -> line_len(Data, Idx + 1)
	end.

count_xmas(Data, LineLen) ->
	count_xmas(Data, LineLen, 0, 0).
count_xmas(Data, _, Idx, Sum) when Idx >= byte_size(Data) ->
	Sum;
count_xmas(Data, LineLen, Idx, Sum) ->
	Dirs = predict_directions(Data, LineLen, Idx),
	MatchedWords = probe_dirs(Data, LineLen, Idx, Dirs),
	count_xmas(
	  Data, 
	  LineLen, 
	  Idx + 1, 
	  Sum + MatchedWords
	).

predict_directions(Data, LineLen, Idx) ->
	Col = Idx rem LineLen,
	Row = Idx div LineLen,	
	RowLen = byte_size(Data) div LineLen,

	WestGap = Col > 2,
	EastGap = (LineLen - Col) > 3,
	NorthGap = Row > 2,
	SouthGap = (RowLen - Row) > 3,

	 [Dir || {Condition, Dir} <- 
        [
		 {WestGap, w},
		 {EastGap, e},
		 {NorthGap, n},
		 {SouthGap, s},
         {SouthGap andalso WestGap, sw}, 
		 {SouthGap andalso EastGap, se}, 
         {NorthGap andalso WestGap, nw},
		 {NorthGap andalso EastGap, ne}
		], 
        Condition].

probe_dirs(Data, LineLen, Idx, Dirs) ->
	probe_dirs(Data, LineLen, Idx, Dirs, 0).
probe_dirs(_, _, _, [], Sum) ->
	Sum;
probe_dirs(Data, LineLen, Idx, [Dir|Dirs], Sum) ->
	probe_dirs(
		Data,
		LineLen,
		Idx,
		Dirs,
		Sum + traverse_dir(Data, LineLen, Idx, Dir, 0)
	).

traverse_dir(_, _, _, _, 4) -> 1;
traverse_dir(Data, LineLen, Idx, Dir, Iter) ->
	Char = binary:at(Data, Idx),
	case Iter of
		0 when Char =/= $X -> 0;
		1 when Char =/= $M -> 0;
		2 when Char =/= $A -> 0;
		3 when Char =/= $S -> 0;
		_ -> traverse_dir(
			   Data,
			   LineLen,
			   Idx + dir_to_int(LineLen, Dir),
			   Dir,
			   Iter + 1
			  )
	end.

dir_to_int(LineLen, Dir) ->
	case Dir of
		w -> -1;
		e -> 1;
		n -> -LineLen;
		s -> LineLen;
		sw -> LineLen - 1;
		se -> LineLen + 1;
		nw -> -LineLen - 1;
		ne -> -LineLen + 1
	end.
2 Likes

Great job tackling this as an Erlang freshman! :clap: Your solution is detailed and clearly thought out - using functions like predict_directions and probe_dirs shows you’re already embracing Erlang’s functional style. Keep it up, and welcome to the Beam! :rocket::christmas_tree: