This topic is about Day 4 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 4 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
Solutions are getting messier :
main(File) ->
{ok, RawData} = file:read_file(File),
[NumbersRaw|CardsRaw] = bin_split(RawData, <<"\n\n">>),
Numbers = [ binary_to_integer(N) || N <- bin_split(NumbersRaw, <<",">>) ],
Cards = [ [ [ binary_to_integer(N)
|| N <- bin_split(Rows, <<" ">>), N =/= <<>> ]
|| Rows <- bin_split(C, <<"\n">>) ]
|| C <- CardsRaw ],
io:format("part 1: ~p~n", [solve1(Numbers, Cards)]),
io:format("part 2: ~p~n", [solve2(Numbers, Cards)]),
ok.
bin_split(Binary, Subj) ->
binary:split(Binary, Subj, [global, trim]).
solve1(Numbers, Cards) ->
{[Winner|_], _NewCards, N, _NewNumbers} = bingo(Numbers, Cards),
score(Winner) * N.
bingo([N|Numbers], Cards) ->
case lists:partition(fun is_winner/1, [ update_card(N, C) || C <- Cards ]) of
{[], NewCards} ->
bingo(Numbers, NewCards);
{Winners, NewCards} ->
{Winners, NewCards, N, Numbers}
end.
update_card(Number, Card) ->
[ [ case X of
Number -> false;
_ -> X
end || X <- Row] || Row <- Card ].
is_winner(Card) ->
check_rows(Card) orelse check_columns(Card).
check_rows(Card) ->
lists:any(fun (Row) -> not lists:any(fun is_integer/1, Row) end, Card).
check_columns([[false|_], [false|_], [false|_], [false|_], [false|_]]) -> true;
check_columns([[_|A], [_|B], [_|C], [_|D], [_|E]]) -> check_columns([A, B, C, D, E]);
check_columns([[], [], [], [], []]) -> false.
score(Card) ->
lists:sum([ N || Row <- Card, N <- Row, is_integer(N) ]).
solve2(Numbers, Cards) ->
case bingo(Numbers, Cards) of
{[Winner], [], N, _NewNumbers} ->
score(Winner) * N;
{_Winners, NewCards, _N, NewNumbers} ->
solve2(NewNumbers, NewCards)
end.
Day 4 in Sesterl - this time I started using optional function arguments for recursion accumulators adventofcode/main.sest at master · michallepicki/adventofcode · GitHub
I’m not a big fan of the puzzles that require wasted effort in either writing a file parser, or else doing significant manipulation of the input data in your favourite editor.
You can get a significant number of results by looking at the input shape:
,
You don’t actually need to do fancy parsing; you can represent each board as a sequence (either a binary, a list) of integers (or use the array
module); you only need to split on " "
and append, until you hit an empty line which means you complete a board. Put all boards themselves put in a list.
On each run, look into each list, and “mark” the matching number if any. If you use a list or a tuple (converted from a list), you can use a non-integer value (e.g. the atom found
); if you use binaries, pick an out-of-bound value (100…255 inclusively).
Checking for a win only requires checking rows (N..N+5
where N
is 1,6,11,…) and columns (N,N+5,N+10,N+15,...
where N
is 1,2,3…) for all entries being your marker. The score of a winning board is the sum of all non-marker values.
It’s one of the tricks for a lot of Advent of Codes: things look like a fancy datastructure (they use tons of matrices), but you can get a lot done with very little parsing if they’re regular-shaped – always a square or a rectangle – by just putting them in a map or binary or list and just hopping around by doing something very similar to C pointer arithmetic (f[row][col] == f[row*width + col]
, with 0-indexing).
Here, I wipped up a quick one showing the principle there using lists:
-module(day4).
-export([main/1]).
main([File]) ->
{ok,Bin} = file:read_file(File),
[RawDraws|Rest] = string:split(Bin, "\n\n", leading),
Draws = [binary_to_integer(B) || B <- string:split(RawDraws, ",", all)],
Boards = [[binary_to_integer(RawNum)
|| RawNum <- re:split(RawBoard, "\\s+", [multiline]),
RawNum =/= <<>>]
|| RawBoard <- string:split(Rest, "\n\n", all)],
io:format("~p~n", [play(Draws, Boards)]),
halt(0).
win(L) ->
lists:any(fun(SL) -> length(SL)==5 end,
[lists:filter(fun(X) -> X==x end, lists:sublist(L, N, 5))
|| N <- [1,6,11,16,21]])
orelse
lists:any(fun(SL) -> length(SL)==5 end,
[lists:filter(fun(X) -> X==x end,
[lists:nth(N,L), lists:nth(N+5,L), lists:nth(N+10,L),
lists:nth(N+15,L), lists:nth(N+20,L)])
|| N <- [1,2,3,4,5]]).
mark([],_) -> [];
mark([X|T],X) -> [x|T];
mark([H|T],X) -> [H|mark(T,X)].
score(L) ->
lists:sum([X || X <- L, X=/=x]).
play([D|Ds], Boards) ->
NewBoards = [mark(B, D) || B <- Boards],
case [B || B <- NewBoards, win(B)] of
[] -> play(Ds, NewBoards);
[W] -> score(W)*D
end.
It’s less effective than it could be because of all the calls to lists:nth/2
being done eagerly, but that’s relatively easy to alter the data structure. The parsing is 6-7 lines of code.
I agree!
Maybe next week I will work on trying to clean a “messy” solution, instead of writing my own.
Did you decide to make it work only on AoC’s input file? Because it seems to me that Part 1 doesn’t work on AoC’s example.
Interesting the different styles of solving that. Here is mine:
Read, split tokenise and convert to numbers with a deep mapping.
Boards are list of list of integers. I attach the transpose to the board for columns.
Bingo steps are done by removing the played number from the lists, counting steps, sorting.
Conveniently I just need to add all integers in a board (everything played is removed) and divide by 2 (because I have the board normal and transposed).
p4() ->
[Nums,Boards] = p4_read("priv/p04.txt"),
Tab = lists:sort([{steps(Nums,B++transpose(B),0),B}
|| B <- Boards]),
{{_,L1,S1}, _}= hd(Tab),
{{_,L2,S2}, _}= lists:last(Tab),
{L1*(S1 div 2), L2*(S2 div 2)}.
steps([], _, Count) ->
{Count + 1, [], 0};
steps([Num|R], Board, Count) ->
B = play(Num, Board),
case bingo(B) of
true -> {Count+1, Num, lists:sum(lists:flatten(B))};
false -> steps(R, B, Count+1)
end.
play(Num, Board) ->
[ [ E || E <- L, E =/= Num ] || L <- Board ].
bingo(Board) ->
lists:any(fun(X) -> X =:= [] end, Board).
p4_read(Fn) ->
{ok, Bin} = file:read_file(Fn),
[Nums|Boards] = string:split(string:trim(Bin), "\n\n", all),
deep_integer([string:split(Nums, ",", all),
[ [ string:lexemes(L, " ")
|| L <- string:split(B, "\n", all) ]
|| B <- Boards]]) .
deep_integer(L) when is_list(L) ->
[ deep_integer(E) || E <- L ];
deep_integer(B) when is_binary(B) ->
binary_to_integer(B).
transpose([[]|_]) ->
[];
transpose(LoL) ->
R = [hd(L) || L <- LoL],
Cs = [tl(L) || L <- LoL],
[R|transpose(Cs)].
For a shorter solution this can be inlined, but its more readable this way
@adolfont example works for me on my code, if you’re on windows - you need to convert line endings to unix, what error do you get?
Your code is here pensandoemelixir/adventofcode/2021/day04_2021 at main · adolfont/pensandoemelixir · GitHub as danilagamma.erl
. I run main/0 (see image below) that contains:
main() ->
main("input_from_description.txt"),
main("input.txt").
and it returns 2226, instead of 4512.
My input_from_description.txt file is here pensandoemelixir/adventofcode/2021/day04_2021/input_from_description.txt at main · adolfont/pensandoemelixir · GitHub
and it was copied, as the name says, from the description.
Here is a bingo module a came up with…
-module(bingo).
-export([new_board/1, play/2, score/1, score/2]).
-record(cell, {val, marked = false}).
-record(board, {grid = #{}, win = false}).
-define(SIZE, 5).
new_board(List) ->
{Board, _} = lists:foldl(fun(X, Acc) -> reducer(X, Acc) end,
{#board{}, 0}, List),
Board.
play(#board{grid = Grid} = Board, Move) ->
Pred = fun(_Coord, #cell{val = Value}) -> Value == Move end,
case maps:filter(Pred, Grid) of
Map when map_size(Map) =:= 0 -> Board;
Map when map_size(Map) =:= 1 ->
[{Coord, Cell}|_T] = maps:to_list(Map),
NewGrid = maps:put(Coord, Cell#cell{marked = true}, Grid),
Board#board{grid = NewGrid, win = is_winning(NewGrid, Coord)}
end.
score({Board, Move}) -> score(Board, Move).
score(#board{grid = Grid}, Move) ->
List = maps:values(maps:filter(fun(_Coord, #cell{marked = Marked}) ->
Marked =:= false
end, Grid)),
X = lists:sum(lists:map(fun(#cell{val = Value}) -> Value end, List)),
X * Move.
reducer(Val, {Board, Current}) ->
Grid = maps:put(to_coord(Current), #cell{val = Val}, Board#board.grid),
{Board#board{grid = Grid}, Current + 1}.
to_coord(Index) ->
{Index div ?SIZE, Index rem ?SIZE}.
is_winning(Grid, {Row, Col}) ->
Pred = fun({_, #cell{marked = Marked}}) -> Marked =:= true end,
lists:all(Pred, get_line(row, Grid, Row)) or
lists:all(Pred, get_line(col, Grid, Col)).
get_line(row, Grid, Index) ->
maps:to_list(maps:filter(fun({Row, _Col}, _Cell) -> Row =:= Index end, Grid));
get_line(col, Grid, Index) ->
maps:to_list(maps:filter(fun({_Row, Col}, _Cell) -> Col =:= Index end, Grid)).
I have been fighting a lot to replace the Elixir Enum module
My full Elixir code is here.
There is an empty whitespace in the end of the example file that shouldn’t be there, removing it makes it return correct result
OK… Here is my messy solution :
Part 1:
part1() ->
{ok, Text} = file:read_file("input"),
[H|T] = split(Text, <<"\n\n">>),
Numbers = split(H, <<",">>),
Cards = [lists:flatten([[ X || X <- split(R, <<" ">>), X /= <<>>] || R <- split(Card, <<"\n">>)]) || Card <- T],
play(Cards, Numbers).
split(Bin, Pattern) -> binary:split(Bin, Pattern, [global, trim]).
play(_, []) -> ok;
play(Cards, [Number|Numbers]) ->
UpdatedCards = check(Cards, Number),
case [Card ||Card <- UpdatedCards, is_win(Card)] of
[] -> play(UpdatedCards, Numbers);
[Winner] -> calculate(Winner, Number)
end.
check([], _) -> [];
check([H|T], Number) -> [is_exist(Number, H) | check(T, Number)].
is_win([]) -> false;
is_win([{_, true}, {_, true}, {_, true}, {_, true}, {_, true}|_]) -> true;
is_win([_, _, _, _, _|T]) -> is_win(T).
is_exist(Item, [Item|Rest]) -> [{Item, true} | is_exist(Item, Rest)];
is_exist(Item, [H|Rest]) -> [H | is_exist(Item, Rest)];
is_exist(_, []) -> [].
calculate(Card, Number) -> binary_to_integer(Number) * lists:sum([ binary_to_integer(C) || C <- Card, is_binary(C)]).
Part 2:
part2() ->
{ok, Text} = file:read_file("input_test"),
[H|T] = split(Text, <<"\n\n">>),
Numbers = split(H, <<",">>),
Cards = [ [ [ X || X <- split(R, <<" ">>), X /= <<>> ] || R <- split(Card, <<"\n">>)] || Card <- T],
[{N, W}|_] = play(Cards, Numbers, []),
calculate(W, N).
split(Bin, Pattern) -> binary:split(Bin, Pattern, [global, trim]).
play([], _, Acc) -> Acc;
play(Cards, [Number|Numbers], Acc) ->
UpdatedCards = check(Cards, Number),
case [Card || Card <- UpdatedCards, is_win(Card)] of
[] ->
case is_win_down(UpdatedCards, UpdatedCards, []) of
[] -> play(UpdatedCards, Numbers, Acc);
Winner -> play(UpdatedCards -- Winner, Numbers, [{Number, Winner} | Acc])
end;
[Winner|_] = Z -> play(UpdatedCards -- Z, Numbers, [{Number, Winner} | Acc])
end.
check([], _) -> [];
check([H|T], Number) -> [[is_exist(Number, R) || R <- H] | check(T, Number)].
is_win([]) -> false;
is_win([[{_, true}, {_, true}, {_, true}, {_, true}, {_, true}|_]|_]) -> true;
is_win([[_, _, _, _, _]|T]) -> is_win(T).
is_win_down([], _, Acc) -> Acc;
is_win_down([[[{_, true}|_],[{_, true}|_],[{_, true}|_],[{_, true}|_],[{_, true}|_]]|T1], [H|T2], Acc) -> is_win_down(T1,T2,[H|Acc]);
is_win_down([[[_|T1],[_|T2],[_|T3],[_|T4],[_|T5]]|T], Cards, Acc) -> is_win_down([[T1,T2,T3,T4,T5]|T], Cards, Acc);
is_win_down([_|T], [_|T], Acc) -> is_win_down(T, T, Acc).
is_exist(Item, [Item|Rest]) -> [{Item, true} | is_exist(Item, Rest)];
is_exist(Item, [H|Rest]) -> [H | is_exist(Item, Rest)];
is_exist(_, []) -> [].
calculate(Card0, Number) ->
Card = lists:flatten(Card0),
binary_to_integer(Number) * lists:sum([ binary_to_integer(C) || C <- Card, is_binary(C)]).
Fixed. Thanks! Later I will try to learn more from your code.