:- module day3. :- interface. :- import_module basics. :- import_module list. :- pred run(part::in, list(string)::in, answer::out) is cc_multi. :- implementation. :- import_module int. :- import_module char. :- import_module string. :- pred split(string::in, string::out, string::out) is semidet. split(X, L, H) :- Len = length(X), even(Len), split(X, Len / 2, L, H). :- pred member(char::out, string::in) is nondet. member(C, S) :- nondet_int_in_range(0, length(S) - 1, I), index(S, I, C). :- pred common(list(string)::in, char::out) is nondet. common([S | Ss], C) :- member(C, S), (Ss = [] ; common(Ss, C)). :- func prio(char) = int. prio(C) = I :- if is_lower(C) then I = to_int(C) - to_int('a') + 1 else I = to_int(C) - to_int('A') + 27. :- type triple(T) ---> t(T, T, T). :- pred threes(list(T)::in, list(triple(T))::out) is semidet. threes(In, Out) :- threes(Out, In, []). :- pred threes(list(triple(T))::out, list(T)::in, list(T)::out) is semidet. threes([]) --> =([]). threes([t(X, Y, Z) | Rest]) --> [X, Y, Z], threes(Rest). :- pred go1(string::in, int::out) is cc_multi. go1(Line, Out) :- if split(Line, Fst, Snd), common([Fst, Snd], C) then Out = prio(C) else die("invalid line: ""%s""", [s(Line)]). :- pred go2(triple(string)::in, int::out) is cc_multi. go2(t(X, Y, Z), Prio) :- if common([X, Y, Z], C) then Prio = prio(C) else die("no item in common in:\n - %s\n - %s\n - %s", [s(X), s(Y), s(Z)]). run(one, Lines, int(sum(Prios))) :- map(go1, Lines, Prios). run(two, Lines, int(sum(Prios))) :- if threes(Lines, Groups) then map(go2, Groups, Prios) else die("%d lines given (not a multiple of 3)", [i(length(Lines))]).