Advent of Code 2020 - Day 2

AoC 2020 Day 2

Day 2 of the Advent of Code evaluates a string against a rule for validity.

Reading the input

read_input(File) ->
    {'ok', Lines} = file:read_file(File),
    [line_to_pw(Line) || Line <- binary:split(Lines, <<"\n">>, ['global']), Line =/= <<>>].

line_to_pw(Line) ->
    {'match', [Min, Max, Char, PW]} = re:run(Line, <<"(\\d+)-(\\d+)\s+(\\w):\s+(.+)$">>, [{'capture', 'all_but_first', 'binary'}]),
    {binary_to_integer(Min, 10), binary_to_integer(Max, 10), Char, PW}.

Here we utilize a regular expression to capture the Min and Max numbers (\d+ - 1 or more digits), the character the limits apply to (\w - just one character), and the password to validate ((.+) everything after the space until the end of the string).

Evaluating passwords

main(_) ->
    PasswordDB = read_input("p2.txt"),
    Valid = lists:foldl(fun count_valid_pw/2, 0, PasswordDB),
    io:format('user', "valid: ~p~n", [Valid]).

count_valid_pw({Min, Max, Char, PW}, ValidCount) ->
    case is_valid_pw(Min, Max, Char, PW) of
	'true' -> ValidCount + 1;
	'false' -> ValidCount
    end.

is_valid_pw(Min, Max, Char, PW) ->
    Count = length(binary:split(PW, Char, ['global']))-1,
    Count >= Min andalso Count =< Max.

After loading the passwords and rules into PasswordDB, we fold over the list and count only those passwords that are valid.

To validate the password, I chose to split the password on the Char value, get the length of the list and decrement by one. For instance:

binary:split(<<"aaa">>, <<"a">>, ['global']).
[<<>>,<<>>,<<>>,<<>>]
Count = 3

binary:split(<<"cdefg">>, <<"b">>, ['global']).
[<<"cdefg">>]
Count = 0

binary:split(<<"ccccccccc">>, <<"c">>, ['global']).
[<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>,<<>>]
Count = 9

Then it is a simple check on Min <= Count <= Max.

Part 2

The big change for part 2 is the is_valid_pw/4 function:

is_valid_pw(FirstPos, SecondPos, Char, PW) ->
    case {char_at(FirstPos, PW), char_at(SecondPos, PW)} of
	{Char, Char} -> 'false';
	{Char, _} -> 'true';
	{_, Char} -> 'true';
	{_, _} -> 'false'
    end.

char_at(Pos, PW) ->
    erlang:binary_part(PW, Pos-1, 1).

Min and Max are re-interpreted as FirstPosition and SecondPosition. We get the character at each position and compare to Char. I explicitly show the truth table via pattern matching on Char but it is effectively an xor operation. See for example:

33> (<<"c">> =:= <<"c">>) xor (<<"c">> =:= <<"c">>).
false
34> (<<"b">> =:= <<"c">>) xor (<<"c">> =:= <<"c">>).
true
35> (<<"c">> =:= <<"c">>) xor (<<"c">> =:= <<"b">>).
true
36> (<<"b">> =:= <<"c">>) xor (<<"c">> =:= <<"b">>).
false

Wrap-up

Not much trouble on this one. Getting the regexp right made the rest mostly took care of itself. Execution time for both parts remains ~0.180s.