Day 4
2020-01-23
This day was straightforward, but it did introduce me to some new data structures. I made use of association lists which can be created from pairs using list_to_assoc/2
(relevant StackOverflow). I think this will turn out to be quite useful in the future. Overall, the data validation pieces a while to implement just because there are so many conditions. I imagined that it would be less verbose to specify something like this in Prolog, but the amount of intermediate variables that need to be declared puts a hamper on things. height_valid/1
was the one predicate that seemed overly verbose because strings need to be converted to chars to strip out the units, then converted into a string again before being cast into an integer. It’d be nice to be able to call multiple predicates in the same statement, but I can’t quite put my finger on what exactly that syntax would look like.
The use of char codes was easier than I anticipated, and it reminded me of my first computer science class of directly manipulating strings. I would rather have done this using regular expressions, but I don’t think regex is built into the swi-prolog standard library.
Another small thing I figured out — I can declare facts directly inside of the predicate. For example, my test data could have been refactored as so:
test_a("a:b
e:f
c:d".)
I’ve also found out a way to make better unit tests on predicates by declaring predicates that can’t be called by the user starting them with :-
. This was handy when I was looking for a tiny bug in the solution for the second part. It turned out I was missing “grn” as a valid eye color!
Solution
% test case for split_entries/2
test_a(X):-
X = "a:b
e:f
c:d".
sample(X):-
X = "ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
byr:1937 iyr:2017 cid:147 hgt:183cm
iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
hcl:#cfa07d byr:1929
hcl:#ae17e1 iyr:2013
eyr:2024
ecl:brn pid:760753108 byr:1931
hgt:179cm
hcl:#cfa07d eyr:2025 pid:166559648
iyr:2011 ecl:brn hgt:59in".
to_dict(Item, K-V):-
split_string(Item, ':', ':', [K,V]).
parse_entries([], [], []).
parse_entries([], Acc, [Acc]).
parse_entries([Head|Tail], Acc, [Acc|Results]):-
% confused on how this predicate works
normalize_space(atom(Normed), Head),
Normed = '',
parse_entries(Tail, [], Results).
parse_entries([Head|Tail], Acc, Results):-
split_string(Head, '\s', '\s', Entries),
maplist(to_dict, Entries, Parsed),
append(Acc, Parsed, NewAcc),
parse_entries(Tail, NewAcc, Results).
keys(["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]).
is_valid_key(Key):-
keys(Keys),
member(Key, Keys).
is_valid(Entry):-
pairs_keys(Entry, Keys),
include(is_valid_key, Keys, ValidKeys),
length(ValidKeys, Size),
keys(MustInclude),
length(MustInclude, Size).
split_entries(Input, Entries):-
split_string(Input, '\n', '\s', Lines),
parse_entries(Lines, [], Entries).
main(Input, Output):-
split_entries(Input, Entries),
include(is_valid, Entries, Valid),
length(Valid, Output).
year_valid(Assoc, Key, Lower, Upper):-
get_assoc(Key, Assoc, Data),
atom_number(Data, Number),
Number >= Lower,
Number =< Upper.
check_height("cm", Data):- Data >= 150, Data =< 193.
check_height("in", Data):- Data >= 59, Data =< 76.
height_valid(Assoc) :-
get_assoc("hgt", Assoc, Data),
string_chars(Data, Chars),
% this can't be the most efficient way, can it?
append(NumberChars, [X,Y], Chars),
string_chars(Unit, [X,Y]),
string_chars(NumberString, NumberChars),
atom_number(NumberString, Number),
check_height(Unit, Number).
valid_color_codes(Char):-
char_code(Char, Code),
char_code('0', LowerDigit),
char_code('9', UpperDigit),
char_code('a', LowerAlpha),
char_code('f', UpperAlpha),
(
Code >= LowerDigit,
Code =< UpperDigit
;
Code >= LowerAlpha,
Code =< UpperAlpha
).
hair_color_valid(Assoc):-
get_assoc("hcl", Assoc, Data),
string_chars(Data, ['#'|Chars]),
length(Chars, 6),
include(valid_color_codes, Chars, ValidChars),
length(ValidChars, 6).
eye_color(["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]).
eye_color_valid(Assoc):-
get_assoc("ecl", Assoc, Data),
eye_color(Color),
member(Data, Color).
pid_valid(Assoc):-
get_assoc("pid", Assoc, Data),
string_chars(Data, Chars),
length(Chars, 9),
maplist(atom_number, Chars, _).
% what a lengthy puzzle
is_data_valid(Entry):-
list_to_assoc(Entry, Assoc),
year_valid(Assoc, "byr", 1920, 2002),
year_valid(Assoc, "iyr", 2010, 2020),
year_valid(Assoc, "eyr", 2020, 2030),
height_valid(Assoc),
hair_color_valid(Assoc),
eye_color_valid(Assoc),
pid_valid(Assoc).
% some simple test cases, not comprehensive
hcl(Data):-
list_to_assoc(["hcl"-Data], Assoc),
hair_color_valid(Assoc).
ecl(Data):-
list_to_assoc(["ecl"-Data], Assoc),
eye_color_valid(Assoc).
pid(Data):-
list_to_assoc(["pid"-Data], Assoc),
pid_valid(Assoc).
:- hcl("#123abc").
:- \+ hcl("#123abz").
:- \+ hcl("123abc").
:- ecl("brn").
:- \+ ecl("wat").
:- pid("000000001").
:- \+ pid("0123456789").
main2(Input, Output):-
split_entries(Input, Entries),
include(is_valid, Entries, ValidEntries),
include(is_data_valid, ValidEntries, ValidDataEntries),
length(ValidDataEntries, Output).