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.