From db18ed32813c139105841077702dd9587697a460 Mon Sep 17 00:00:00 2001 From: Jeremy Kaplan Date: Sat, 19 Dec 2020 00:31:26 -0800 Subject: [PATCH] Day 19 Part 2 --- 2020/day19/day19.ex | 173 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 27 deletions(-) diff --git a/2020/day19/day19.ex b/2020/day19/day19.ex index 43e0d5a..edc9b7e 100644 --- a/2020/day19/day19.ex +++ b/2020/day19/day19.ex @@ -4,24 +4,28 @@ defmodule RBNF do |> Enum.into(%{}, fn [name, rbnf] -> { name, - {:alt, - String.split(rbnf, " | ") - |> Enum.map(fn text -> - seq = - String.split(text, " ") - |> Enum.map(fn chars -> - cond do - String.match?(chars, ~r/^\d+$/) -> {:ref, chars} - String.match?(chars, ~r/^".*"$/) -> {:lit, String.slice(chars, 1..-2)} - end - end) - - {:seq, seq} - end)} + make_rule(rbnf) } end) end + def make_rule(str) do + {:alt, + String.split(str, " | ") + |> Enum.map(fn text -> + seq = + String.split(text, " ") + |> Enum.map(fn chars -> + cond do + String.match?(chars, ~r/^\d+$/) -> {:ref, chars} + String.match?(chars, ~r/^".*"$/) -> {:lit, String.slice(chars, 1..-2)} + end + end) + + {:seq, seq} + end)} + end + # Returns {matched_text, remaining_text} def parse(grammar, rule, text) do case rule do @@ -43,7 +47,7 @@ defmodule RBNF do {nil, nil} else case parse(grammar, rule, buffer) do - {nil, extra} -> {nil, nil} + {nil, _} -> {nil, nil} {matched, rest} -> {matched, rest} end end @@ -69,6 +73,59 @@ defmodule RBNF do nil -> {nil, text} {matched, rest} -> {matched, rest} end + + {:plus, repeated} -> + case parse(grammar, repeated, text) do + {nil, _} -> {nil, text} + {matched, rest} -> star_iter(grammar, repeated, matched, rest) + end + + {:paired, r1, r2} -> + {count, prefix, rest} = count_iter(grammar, r1, "", 0, text) + + if count == 0 do + {nil, text} + else + new_rule = {:seq, List.duplicate(r2, count)} + + case parse(grammar, new_rule, rest) do + {nil, _} -> {nil, text} + {suffix, remaining} -> {prefix <> suffix, remaining} + end + end + + {:left_sided, left, right} -> + {count, prefix, rest} = count_iter(grammar, left, "", 0, text) + + if count < 2 do + # No extra lefts to match with + {nil, text} + else + new_rule = {:at_most, right, count - 1} + + case parse(grammar, new_rule, rest) do + {nil, _} -> {nil, text} + {suffix, remaining} -> {prefix <> suffix, remaining} + end + end + + {:at_most, r, count} -> + new_rule = {:alt, Enum.map(count..1, &{:seq, List.duplicate(r, &1)})} + parse(grammar, new_rule, text) + end + end + + defp star_iter(grammar, rule, prefix, text) do + case parse(grammar, rule, text) do + {nil, _} -> {prefix, text} + {matched, rest} -> star_iter(grammar, rule, prefix <> matched, rest) + end + end + + defp count_iter(grammar, rule, prefix, count, text) do + case parse(grammar, rule, text) do + {nil, _} -> {count, prefix, text} + {matched, rest} -> count_iter(grammar, rule, prefix <> matched, count + 1, rest) end end end @@ -81,18 +138,53 @@ defmodule Day19 do defp test_input do """ - 0: 4 1 5 - 1: 2 3 | 3 2 - 2: 4 4 | 5 5 - 3: 4 5 | 5 4 - 4: "a" - 5: "b" + 42: 9 14 | 10 1 + 9: 14 27 | 1 26 + 10: 23 14 | 28 1 + 1: "a" + 11: 42 31 + 5: 1 14 | 15 1 + 19: 14 1 | 14 14 + 12: 24 14 | 19 1 + 16: 15 1 | 14 14 + 31: 14 17 | 1 13 + 6: 14 14 | 1 14 + 2: 1 24 | 14 4 + 0: 8 11 + 13: 14 3 | 1 12 + 15: 1 | 14 + 17: 14 2 | 1 7 + 23: 25 1 | 22 14 + 28: 16 1 + 4: 1 1 + 20: 14 14 | 1 15 + 3: 5 14 | 16 1 + 27: 1 6 | 14 18 + 14: "b" + 21: 14 1 | 1 14 + 25: 1 1 | 1 14 + 22: 14 14 + 8: 42 + 26: 14 22 | 1 20 + 18: 15 15 + 7: 14 5 | 1 21 + 24: 14 1 - ababbb - bababa - abbbab - aaabbb - aaaabbb + abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa + bbabbbbaabaabba + babbbbaabbbbbabbbbbbaabaaabaaa + aaabbbbbbaaaabaababaabababbabaaabbababababaaa + bbbbbbbaaaabbbbaaabbabaaa + bbbababbbbaaaaaaaabbababaaababaabab + ababaaaaaabaaab + ababaaaaabbbaba + baabbaaaabbaaaababbaababb + abbbbabbbbaaaababbbbbbaaaababb + aaaaabbaabaaaaababaa + aaaabbaaaabbaaa + aaaabbaabbaaaaaaabbbabbbaaabbaabaaa + babaaabbbaaabaababbaabababaaab + aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba """ end @@ -116,7 +208,33 @@ defmodule Day19 do Enum.count(messages, fn msg -> case RBNF.parse(grammar, {:ref, "0"}, msg) do - {msg, ""} -> true + {_, ""} -> true + _ -> false + end + end) + end + + def part2 do + {rules, messages} = read_input() |> parse_input() + + grammar = + RBNF.build_grammar(rules) + # 8: 42 | 42 8 = 42+ + |> Map.put("8", {:plus, {:ref, "42"}}) + # 11: 42 31 | 42 11 31 = same number of 42 and 31 (at least one pair) + |> Map.put( + "11", + {:paired, {:ref, "42"}, {:ref, "31"}} + ) + # 0: 8 11 = 42+ (matched 42 31) = 42 42{N} 31{1,N-1} = 42{M} 31{1,M-1} + |> Map.put( + "0", + {:left_sided, {:ref, "42"}, {:ref, "31"}} + ) + + Enum.count(messages, fn msg -> + case RBNF.parse(grammar, {:ref, "0"}, msg) do + {_, ""} -> true _ -> false end end) @@ -124,3 +242,4 @@ defmodule Day19 do end Day19.part1() |> IO.inspect() +Day19.part2() |> IO.inspect()