diff --git a/2021/README.md b/2021/README.md index 6ff48e7..b5243c9 100644 --- a/2021/README.md +++ b/2021/README.md @@ -17,7 +17,7 @@ | 11 | ✓ | ✓ | [Crystal] | | 12 | ✓ | ✓ | [Python] | | 13 | ✓ | ✓ | [C++] | -| 14 | | | | +| 14 | ✓ | ✓ | [Erlang] | | 15 | | | | | 16 | | | | | 17 | | | | @@ -49,3 +49,4 @@ [crystal]: https://crystal-lang.org [python]: https://www.python.org [c++]: https://isocpp.org +[erlang]: https://www.erlang.org diff --git a/2021/day-14/.gitignore b/2021/day-14/.gitignore new file mode 100644 index 0000000..31a6f24 --- /dev/null +++ b/2021/day-14/.gitignore @@ -0,0 +1,2 @@ +*.beam +*.dump diff --git a/2021/day-14/Justfile b/2021/day-14/Justfile new file mode 100644 index 0000000..16fb754 --- /dev/null +++ b/2021/day-14/Justfile @@ -0,0 +1,6 @@ +@part PART INPUT_FILE="inputs/puzzle.txt": + erl -compile common.erl + escript part_{{PART}}.erl {{INPUT_FILE}} + +clean: + rm -f *.beam *.dump diff --git a/2021/day-14/common.erl b/2021/day-14/common.erl new file mode 100644 index 0000000..8ad79d9 --- /dev/null +++ b/2021/day-14/common.erl @@ -0,0 +1,130 @@ +-module(common). + +-export([parse_file/1, apply_rules/3, tally_elements/1, polymerization/3]). + +-import(lists, [append/2]). +-import(string, [chomp/1, split/2, slice/2, slice/3]). + + +% File parsing + +parse_rule(Raw) -> + [S, I] = split(chomp(Raw), " -> "), + #{{slice(S, 0, 1), slice(S, 1, 1)} => I}. + +parse_seq(Sequence, Acc) -> + case slice(Sequence, 0, 1) of + "" -> Acc; + First -> parse_seq(slice(Sequence, 1), append(Acc, [First])) + end. + +parse_lines(File, Mode, Sequence, Rules) -> + case {Mode, file:read_line(File)} of + {read_seq, {ok, "\n"}} -> + parse_lines(File, read_rules, Sequence, Rules); + + {read_seq, {ok, Val}} -> + NextSequence = parse_seq(chomp(Val), []), + parse_lines(File, read_seq, NextSequence, Rules); + + {read_rules, {ok, Val}} -> + NextRules = maps:merge(Rules, parse_rule(Val)), + parse_lines(File, read_rules, Sequence, NextRules); + + {read_rules, eof} -> + {Sequence, Rules} + end. + +parse_file(Path) -> + {ok, File} = file:open(Path, [read]), + parse_lines(File, read_seq, [], #{}). + + +% Helper functions + +each_cons2(List = [A, B | _], F) when is_function(F) -> + Result = F(A, B), + each_cons2(tl(List), [Result], F). + +each_cons2([A, B], Acc, F) when is_function(F) -> + Result = F(A, B), + lists:append(Acc, [Result]); +each_cons2(List = [A, B | _], Acc, F) when is_function(F) -> + Result = F(A, B), + each_cons2(tl(List), lists:append(Acc, [Result]), F). + +succ(V) -> V + 1. + + +% Slow algorithm + +apply_rules(Sequence, _, 0) -> + Sequence; +apply_rules(Sequence, Rules, Times) -> + NextSequence = apply_rules(Sequence, Rules), + apply_rules(NextSequence, Rules, Times - 1). + +apply_rules(Sequence, Rules) -> + Cons = each_cons2(Sequence, fun (A, B) -> + [A, maps:get({A, B}, Rules), B] + end), + flatten_seq(Cons, []). + +tally_elements(Sequence) -> + tally_elements(Sequence, #{}). + +tally_elements([], Acc) -> + Acc; +tally_elements([Head | Tail], Acc) -> + Tally = maps:put(Head, 1 + maps:get(Head, Acc, 0), Acc), + tally_elements(Tail, Tally). + +flatten_seq([[A, B, C]], Acc) -> + lists:append(Acc, [A, B, C]); +flatten_seq([[A, B | _] | Tail], Acc) -> + flatten_seq(Tail, lists:append(Acc, [A, B])). + + +% Fast algorithm + +apply_rule(Pair, Rules) -> + maps:get(Pair, Rules). + +update_tally({A, R, B}, Occ, Tally) -> + AddOccurences = fun(V) -> V + Occ end, + Tmp = maps:update_with({A, R}, AddOccurences, Occ, Tally), + maps:update_with({R, B}, AddOccurences, Occ, Tmp). + +polymerization(Sequence, Rules, Times) when is_list(Sequence) -> + Cons = each_cons2(Sequence, fun(A, B) -> {A, B} end), + + % first tally up all entries and build the counters + {Tally, Counter} = lists:foldl(fun(Pair = {A, _}, {Tal, Cnt}) -> + T = maps:update_with(Pair, fun succ/1, 1, Tal), + C = maps:update_with(A, fun succ/1, 1, Cnt), + {T, C} + end, {#{},#{}}, Cons), + + % because the last one got skipped we have to add it manually + {_, Last} = lists:last(Cons), + Counter2 = maps:update_with(Last, fun succ/1, 1, Counter), + + polymerization(Rules, Tally, Counter2, Times). + +polymerization(_, _, Counter, 0) -> + Counter; + +polymerization(Rules, Tally, Counter, Times) -> + {NewTally, NewCounter} = polymerization_step(Rules, Tally, Counter), + polymerization(Rules, NewTally, NewCounter, Times - 1). + +polymerization_step(Rules, Tally, Counter) -> + maps:fold(fun({A, B}, Occ, {NewTally, NewCounter}) -> + Re = apply_rule({A, B}, Rules), + + % Update occurance and element counters + NewTally2 = update_tally({A, Re, B}, Occ, NewTally), + NewCounter2 = maps:update_with(Re, fun(V) -> V + Occ end, 1, NewCounter), + + {NewTally2, NewCounter2} + end, {#{}, Counter}, Tally). diff --git a/2021/day-14/inputs/puzzle.txt b/2021/day-14/inputs/puzzle.txt new file mode 100644 index 0000000..3a2c7ec --- /dev/null +++ b/2021/day-14/inputs/puzzle.txt @@ -0,0 +1,102 @@ +KOKHCCHNKKFHBKVVHNPN + +BN -> C +OS -> K +BK -> C +KO -> V +HF -> K +PS -> B +OK -> C +OC -> B +FH -> K +NV -> F +HO -> H +KK -> H +CV -> P +SC -> C +FK -> N +VV -> F +FN -> F +KP -> O +SB -> O +KF -> B +CH -> K +VF -> K +BH -> H +KV -> F +CO -> N +PK -> N +NH -> P +NN -> C +PP -> H +SH -> N +VO -> O +NC -> F +BC -> B +HC -> H +FS -> C +PN -> F +CK -> K +CN -> V +HS -> S +CB -> N +OF -> B +OV -> K +SK -> S +HP -> C +SN -> P +SP -> B +BP -> C +VP -> C +BS -> K +FV -> F +PH -> P +FF -> P +VK -> F +BV -> S +VB -> S +BF -> O +BB -> H +OB -> B +VS -> P +KB -> P +SF -> N +PF -> S +HH -> P +KN -> K +PC -> B +NB -> O +VC -> P +PV -> H +KH -> O +OP -> O +NF -> K +HN -> P +FC -> H +PO -> B +OH -> C +ON -> N +VN -> B +VH -> F +FO -> B +FP -> B +BO -> H +CC -> P +CS -> K +NO -> V +CF -> N +PB -> H +KS -> P +HK -> S +HB -> K +HV -> O +SV -> H +CP -> S +NP -> N +FB -> B +KC -> V +NS -> P +OO -> V +SO -> O +NK -> K +SS -> H diff --git a/2021/day-14/inputs/sample.txt b/2021/day-14/inputs/sample.txt new file mode 100644 index 0000000..b5594dd --- /dev/null +++ b/2021/day-14/inputs/sample.txt @@ -0,0 +1,18 @@ +NNCB + +CH -> B +HH -> N +CB -> H +NH -> C +HB -> C +HC -> B +HN -> C +NN -> C +BH -> H +NC -> B +NB -> B +BN -> B +BB -> N +BC -> B +CC -> N +CN -> C diff --git a/2021/day-14/part_one.erl b/2021/day-14/part_one.erl new file mode 100644 index 0000000..5fc6e7f --- /dev/null +++ b/2021/day-14/part_one.erl @@ -0,0 +1,16 @@ +-module(part_one). + +-export([main/1]). + +-import(common, [parse_file/1, polymerization/3]). + +main(Args) -> + Path = lists:last(Args), + {Sequence, Rules} = parse_file(Path), + + Counter = polymerization(Sequence, Rules, 10), + Values = maps:values(Counter), + Max = lists:max(Values), + Min = lists:min(Values), + + io:format("~p~n", [Max - Min]). diff --git a/2021/day-14/part_two.erl b/2021/day-14/part_two.erl new file mode 100644 index 0000000..67d84fd --- /dev/null +++ b/2021/day-14/part_two.erl @@ -0,0 +1,16 @@ +-module(part_two). + +-export([main/1]). + +-import(common, [parse_file/1, polymerization/3]). + +main(Args) -> + Path = lists:last(Args), + {Sequence, Rules} = parse_file(Path), + + Counter = polymerization(Sequence, Rules, 40), + Values = maps:values(Counter), + Max = lists:max(Values), + Min = lists:min(Values), + + io:format("~p~n", [Max - Min]). diff --git a/README.md b/README.md index 8d6de97..bbc390f 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ - [2018](2018/README.md) (0% completed) - [2019](2019/README.md) (0% completed) - [2020](2020/README.md) (20% completed) -- [2021](2021/README.md) (52% completed) +- [2021](2021/README.md) (56% completed)