diff --git a/memo.md b/memo.md index 4bd0397..4471e2d 100644 --- a/memo.md +++ b/memo.md @@ -1 +1,343 @@ # Step1 + +## アプローチ + +* 括弧の種類ごとにスタックを用意しておく + * 開始かっこが出てきた時はスタックにappend + * 閉じ括弧が出てきた時はスタックからpop + * 最終的にスタックが空になっていたらおk +* 再帰でもループでもできそう +* 括弧の種類ごとに個数をカウントしておくのでもいいか. + * マイナスになった時点でinvalid + +## Code1 (括弧の種類を数える方法, Wrong Answer) + +* 問題文の条件`Open brackets must be closed in the correct order`を満たさず. +* 結局一つのstackに入れていくのが良さそう + +```python +class Solution: + def isValid(self, s: str) -> bool: + parentheses1 = 0 + parentheses2 = 0 + parentheses3 = 0 + for i in range(len(s)): + if parentheses1 < 0 or parentheses2 < 0 or parentheses3 < 0: + return False + if s[i] == "(": + parentheses1 += 1 + elif s[i] == "{": + parentheses2 += 1 + elif s[i] == "[": + parentheses3 += 1 + elif s[i] == ")": + parentheses1 -= 1 + elif s[i] == "}": + parentheses2 -= 1 + elif s[i] == "]": + parentheses3 -= 1 + if parentheses1 == 0 and parentheses2 == 0 and parentheses3 == 0: + return True + return False + +``` + +## Code2 (stack) + +```python +class Solution: + def isValid(self, s: str) -> bool: + unclosed_brackets = [] + brackets_pairs = { + "(" : ")", + "{" : "}", + "[" : "]" + } + for i in range(len(s)): + if s[i] in brackets_pairs.keys(): + unclosed_brackets.append(s[i]) + else: + if len(unclosed_brackets) == 0: + return False + last_unclosed_bracket = unclosed_brackets.pop(-1) + if brackets_pairs[last_unclosed_bracket] != s[i]: + return False + if len(unclosed_brackets) == 0: + return True + return False +``` + +## Code3 (再帰) + +* `return True`や`return False`が交互に出現するのが気持ち悪かったので, `return True`が最後に登場するようにした. +* 途中計算に使う値を変数に切り出して読みやすくした + +```python +BRACKET_PAIRS = { + "(" : ")", + "{" : "}", + "[" : "]" +} +class Solution: + def _isValid(self, s, idx, stack): + if idx == len(s): + if len(stack) == 0: + return True + return False + if s[idx] in BRACKET_PAIRS.keys(): + stack.append(s[idx]) + return self._isValid(s, idx + 1, stack) + if len(stack) == 0: + return False + last_unclosed_bracket = stack.pop(-1) + if BRACKET_PAIRS[last_unclosed_bracket] != s[idx]: + return False + return self._isValid(s, idx + 1, stack) + + def isValid(self, s: str) -> bool: + return self._isValid(s, 0, []) +``` + +# Step2 + +## Code2-2 (stack) + +```python +class Solution: + def isValid(self, s: str) -> bool: + unclosed_brackets = [] + parentheses_pairs = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brackets = parentheses_pairs.keys() + for i in range(len(s)): + if s[i] in open_brackets: + unclosed_brackets.append(s[i]) + continue + if len(unclosed_brackets) == 0: + return False + last_unclosed_bracket = unclosed_brackets.pop(-1) + expected_close_bracket = parentheses_pairs[last_unclosed_bracket] + if s[i] != expected_close_bracket: + return False + if len(unclosed_brackets) > 0: + return False + return True +``` + +# Step3 + +## Code3-2 (stack) + +```python +class Solution: + def isValid(self, s: str) -> bool: + unclosed_open_brackets = [] + bracket_pairs = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brakets = bracket_pairs.keys() + for i in range(len(s)): + if s[i] in open_brakets: + unclosed_open_brackets.append(s[i]) + continue + if len(unclosed_open_brackets) == 0: + return False + last_open_bracket = unclosed_open_brackets.pop(-1) + expected_close_bracket = bracket_pairs[last_open_bracket] + if s[i] != expected_close_bracket: + return False + if len(unclosed_open_brackets) > 0: + return False + return True + +``` + +# Step4/5 + +* 再帰降下法の実装をした. + * [参考](https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.5ynll0rwu02h) +* 自然言語で解釈をちゃんとできたらバグなく実装できた. +* ただ, `nonlocal`な変数として処理をしている`idx`を持つのは初見だと思いつかなさそう. +* 自然言語での動作の説明 + * `check_chunk_valid`は, 先頭の開きかっこから始まって対応する閉じ括弧まで(以降はchunkと呼ぶ)が有効かどうかを判定する関数. + * chunkのなかは, 複数のchunkで構成されることもあるし, 単一のchunkであることもあるし, 何もないこともある + * それぞれの具体例 + * ( () [] {} ) + * ( {([])} ) + * ( ) + * これら全てに対応する, かつ`check_chunk_valid`を再利用するためには, 対応する閉じかっこが出てくるまでこの関数を呼びたい. + * `isValid`の入力は, 同じく複数チャンクからなる可能性があるので似た形式のループで`check_chunk_valid`を呼んであげる. + * 以下の形のコードは2回出現する + ```python + while processing_idx < len(s): + if not check_chunk_valid(): + return False + ``` + + + +```python +class Solution: + def isValid(self, s: str) -> bool: + open_to_close = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brackets = open_to_close.keys() + processing_idx = 0 + + def check_chunk_valid(): + nonlocal processing_idx + if s[processing_idx] not in open_brackets: + return False + expected_end_bracket = open_to_close[s[processing_idx]] + processing_idx += 1 + while processing_idx < len(s) and s[processing_idx] != expected_end_bracket: + if not check_chunk_valid(): + return False + if processing_idx >= len(s): + return False + processing_idx += 1 + return True + + while processing_idx < len(s): + if not check_chunk_valid(): + return False + + return True +``` + +# Step6/7/8 (LL(1)) + +* LL(1)の実装をした. +* アルゴリズムとかは[ブログ](https://kazuki.jp.net/archives/456)にまとめた. +* `follow`で循環が起きているとバグりそうだけど, うまいこと対処する方法が思いつかなかった. + * A -> aBC + * first(C)が"#"を含む時, follow(A)を計算して, follow(B)に追加する + * B -> aAC + * follow(B)を計算して, follow(A)に追加する + * このような生成規則が与えられるとバグりそう + +```python +class Parser: + def __init__(self, terminals, nonterminals, rules, start_symbol): + self.terminals = terminals + self.nonterminals = nonterminals + self.rules = rules + self.start_symbol = start_symbol + self.follow_memo = {} + self.parsing_table = {} + self._construct_parsing_table() + + def first(self, string): + if len(string) == 1: + if string == "#": + return set(["#"]) + if string in self.terminals: + return set([string]) + result = set() + for produced_string in self.rules[string]: + result = result | self.first(produced_string) + return result + + result = set() + for symbol in string: + symbol_first_result = self.first(symbol) + if "#" not in symbol_first_result: + result = result | symbol_first_result + return result + symbol_first_result.remove("#") + result = result | symbol_first_result + result.add("#") + return result + + def follow(self, nonterminal): + if nonterminal not in self.nonterminals: + raise ValueError(f"{nonterminal} is not non-terminal") + if nonterminal in self.follow_memo: + return self.follow_memo[nonterminal] + result = set() if nonterminal != self.start_symbol else set(["$"]) + for production_head, produced_strings in self.rules.items(): + for produced_string in produced_strings: + nonterminal_idx = produced_string.find(nonterminal) + if nonterminal_idx == -1: + continue + while nonterminal_idx != -1: + remaining_string = produced_string[nonterminal_idx + 1:] + if remaining_string: + remaining_string_first_res = self.first(remaining_string) + if "#" not in remaining_string_first_res: + result = result | remaining_string_first_res + else: + production_head_follow_result = self.follow(production_head) if production_head != nonterminal else set() + result = result | production_head_follow_result | (remaining_string_first_res - set(["#"])) + else: + production_head_follow_result = self.follow(production_head) if production_head != nonterminal else set() + result = result | production_head_follow_result + nonterminal_idx = produced_string.find(nonterminal, nonterminal_idx + 1) + self.follow_memo[nonterminal] = result + return result + + def _construct_parsing_table(self): + for nonterminal in self.nonterminals: + self.parsing_table[nonterminal] = {} + for terminal in self.terminals + ["$"]: + self.parsing_table[nonterminal][terminal] = [] + for production_head, produced_strings in self.rules.items(): + for produced_string in produced_strings: + produced_string_first_result = self.first(produced_string) + if "#" in produced_string_first_result: + head_follow_result = self.follow(production_head) + for terminal in head_follow_result: + self.parsing_table[production_head][terminal].append({production_head : produced_string}) + produced_string_first_result.remove("#") + for terminal in produced_string_first_result: + self.parsing_table[production_head][terminal].append({production_head : produced_string}) + # print(self.parsing_table) + return + + def parse(self, input): + end_idx = len(input) + input = input + "$" + stack = ["$", self.start_symbol] + processing_idx = 0 + while stack[-1] != "$": + symbol = stack.pop() + input_char = input[processing_idx] + if symbol == input_char: + processing_idx += 1 + continue + if symbol in self.terminals: + return False + if not self.parsing_table[symbol][input_char]: + return False + production_rules = self.parsing_table[symbol][input_char] + if len(production_rules) >= 2: + return False + production_rule = production_rules[0] + produced_string = production_rule[symbol] + if produced_string != "#": + for produced_char in produced_string[::-1]: + stack.append(produced_char) + return processing_idx == end_idx + +class Solution: + def isValid(self, s: str) -> bool: + + nonterminals = ["S", "C"] + terminals = ["(", ")", "{", "}", "[", "]", "e"] + rules = { + "S" : ["CS","#"], + "C" : ["(S)", "{S}","[S]"] + } + start_symbol = "S" + parser = Parser(terminals, nonterminals, rules, start_symbol) + return parser.parse(s) + +``` \ No newline at end of file diff --git a/step1-1.py b/step1-1.py new file mode 100644 index 0000000..d820735 --- /dev/null +++ b/step1-1.py @@ -0,0 +1,24 @@ +class Solution: + def isValid(self, s: str) -> bool: + parentheses1 = 0 + parentheses2 = 0 + parentheses3 = 0 + for i in range(len(s)): + if parentheses1 < 0 or parentheses2 < 0 or parentheses3 < 0: + return False + if s[i] == "(": + parentheses1 += 1 + elif s[i] == "{": + parentheses2 += 1 + elif s[i] == "[": + parentheses3 += 1 + elif s[i] == ")": + parentheses1 -= 1 + elif s[i] == "}": + parentheses2 -= 1 + elif s[i] == "]": + parentheses3 -= 1 + if parentheses1 == 0 and parentheses2 == 0 and parentheses3 == 0: + return True + return False + \ No newline at end of file diff --git a/step1-2.py b/step1-2.py new file mode 100644 index 0000000..387dbe3 --- /dev/null +++ b/step1-2.py @@ -0,0 +1,20 @@ +class Solution: + def isValid(self, s: str) -> bool: + unclosed_brackets = [] + brackets_pairs = { + "(" : ")", + "{" : "}", + "[" : "]" + } + for i in range(len(s)): + if s[i] in brackets_pairs.keys(): + unclosed_brackets.append(s[i]) + else: + if len(unclosed_brackets) == 0: + return False + last_unclosed_bracket = unclosed_brackets.pop(-1) + if brackets_pairs[last_unclosed_bracket] != s[i]: + return False + if len(unclosed_brackets) == 0: + return True + return False \ No newline at end of file diff --git a/step1-3.py b/step1-3.py new file mode 100644 index 0000000..40c4a71 --- /dev/null +++ b/step1-3.py @@ -0,0 +1,23 @@ +BRACKET_PAIRS = { + "(" : ")", + "{" : "}", + "[" : "]" +} +class Solution: + def _isValid(self, s, idx, stack): + if idx == len(s): + if len(stack) == 0: + return True + return False + if s[idx] in BRACKET_PAIRS.keys(): + stack.append(s[idx]) + return self._isValid(s, idx + 1, stack) + if len(stack) == 0: + return False + last_unclosed_bracket = stack.pop(-1) + if BRACKET_PAIRS[last_unclosed_bracket] != s[idx]: + return False + return self._isValid(s, idx + 1, stack) + + def isValid(self, s: str) -> bool: + return self._isValid(s, 0, []) \ No newline at end of file diff --git a/step2-2.py b/step2-2.py new file mode 100644 index 0000000..35b1a37 --- /dev/null +++ b/step2-2.py @@ -0,0 +1,22 @@ +class Solution: + def isValid(self, s: str) -> bool: + unclosed_brackets = [] + parentheses_pairs = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brackets = parentheses_pairs.keys() + for i in range(len(s)): + if s[i] in open_brackets: + unclosed_brackets.append(s[i]) + continue + if len(unclosed_brackets) == 0: + return False + last_unclosed_bracket = unclosed_brackets.pop(-1) + expected_close_bracket = parentheses_pairs[last_unclosed_bracket] + if s[i] != expected_close_bracket: + return False + if len(unclosed_brackets) > 0: + return False + return True \ No newline at end of file diff --git a/step3-2.py b/step3-2.py new file mode 100644 index 0000000..adcd0b9 --- /dev/null +++ b/step3-2.py @@ -0,0 +1,22 @@ +class Solution: + def isValid(self, s: str) -> bool: + unclosed_open_brackets = [] + bracket_pairs = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brakets = bracket_pairs.keys() + for i in range(len(s)): + if s[i] in open_brakets: + unclosed_open_brackets.append(s[i]) + continue + if len(unclosed_open_brackets) == 0: + return False + last_open_bracket = unclosed_open_brackets.pop(-1) + expected_close_bracket = bracket_pairs[last_open_bracket] + if s[i] != expected_close_bracket: + return False + if len(unclosed_open_brackets) > 0: + return False + return True diff --git a/step4-3.py b/step4-3.py new file mode 100644 index 0000000..fdc2e6a --- /dev/null +++ b/step4-3.py @@ -0,0 +1,30 @@ +class Solution: + def isValid(self, s: str) -> bool: + processing_idx = 0 + open_to_close = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brackets = open_to_close.keys() + + def parentheses(): + nonlocal processing_idx + if s[processing_idx] not in open_brackets: + return False + expected_close_bracket = open_to_close[s[processing_idx]] + processing_idx += 1 + while processing_idx < len(s) and s[processing_idx] != expected_close_bracket: + is_valid = parentheses() + if not is_valid: + return False + if processing_idx < len(s): + processing_idx += 1 + return True + return False + + while processing_idx < len(s): + is_valid = parentheses() + if not is_valid: + return False + return True \ No newline at end of file diff --git a/step5-3.py b/step5-3.py new file mode 100644 index 0000000..e1ec3e8 --- /dev/null +++ b/step5-3.py @@ -0,0 +1,29 @@ +class Solution: + def isValid(self, s: str) -> bool: + open_to_close = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brackets = open_to_close.keys() + processing_idx = 0 + + def check_chunk_valid(): + nonlocal processing_idx + if s[processing_idx] not in open_brackets: + return False + expected_end_bracket = open_to_close[s[processing_idx]] + processing_idx += 1 + while processing_idx < len(s) and s[processing_idx] != expected_end_bracket: + if not check_chunk_valid(): + return False + if processing_idx >= len(s): + return False + processing_idx += 1 + return True + + while processing_idx < len(s): + if not check_chunk_valid(): + return False + + return True \ No newline at end of file diff --git a/step6-3.py b/step6-3.py new file mode 100644 index 0000000..c3b7f41 --- /dev/null +++ b/step6-3.py @@ -0,0 +1,123 @@ +# バグありコード. Step7で修正 +class Parser: + def __init__(self): + self.V = ["S", "C"] + self.T = ["(", ")", "{", "}", "[", "]", ""] + self.P = { + "S" : ["CS",""], + "C" : ["(S)", "{S}","[S]"] + } + self.S = "S" + self.first_map = {} + self.follow_map = {} + self.parsing_table = {} + self._construct_follow_map() + self._construct_parsing_table() + + def first(self, alpha): + if alpha in self.first_map: + return self.first_map[alpha] + if alpha == "": + return set([""]) + result = set() + for i in range(len(alpha)): + symbol = alpha[i] + if symbol in self.T: + result.add(symbol) + self.first_map[alpha] = result + return result + if symbol not in self.V: + raise ValueError(f"the input word [{alpha}] begins with the character not defined in this language.") + if symbol in self.V: + ith_first_result = set() + for produced in self.P[symbol]: + ith_first_result = ith_first_result.union(self.first(produced)) + if "" not in ith_first_result: + result = result.union(ith_first_result) + self.first_map[alpha] = result + return result + ith_first_result.remove("") + result = result.union(ith_first_result) + result.add("") + self.first_map[alpha] = result + return result + + def _construct_follow_map(self): + self.follow_map[self.S] = set(["$"]) + for production_head, produced_list in self.P.items(): + for produced in produced_list: + for processing_idx, symbol in enumerate(produced): + if symbol in self.T: + continue + if symbol in self.V: + if symbol not in self.follow_map: + self.follow_map[symbol] = set() + remaining_string = produced[processing_idx+1:] + remaining_string_first_result = self.first(remaining_string) + if remaining_string == "" or "" in remaining_string_first_result: + self.follow_map[symbol] = self.follow_map[symbol].union(self.follow_map[production_head]) + if "" in remaining_string_first_result: + remaining_string_first_result.remove("") + self.follow_map[symbol] = self.follow_map[symbol].union(remaining_string_first_result) + return + + def follow(self, A): + if A not in self.V: + raise ValueError(f"the input variable [{A}] is not a variable used in this language") + return self.follow_map[A] + + def _construct_parsing_table(self): + for non_terminal in self.V: + self.parsing_table[non_terminal] = {} + for terminal in self.T + ["$"]: + self.parsing_table[non_terminal][terminal] = [] + for production_head, produced_list in self.P.items(): + for produced in produced_list: + produced_first_result = self.first(produced) + for terminal in produced_first_result: + self.parsing_table[production_head][terminal].append({production_head : produced}) + if "" in produced_first_result: + head_follow_result = self.follow(production_head) + for terminal in head_follow_result: + self.parsing_table[production_head][terminal].append({production_head : produced}) + print(self.parsing_table) + return + + def parse(self, input): + for word in input: + if word not in self.T: + # print(f"word [{word}] is not the terminal used in this language") + return False + input = input + "$" + stack = ["$", self.S] + processing_idx = 0 + while stack[-1] != "$": + symbol = stack.pop() + word = input[processing_idx] + if symbol == word: + processing_idx += 1 + continue + if symbol in self.T: + # print(f"The given input is not this language") + return False + if not self.parsing_table[symbol][word]: + # print(f"There is no way to parse the symbol [{symbol}] with preceeding word [{word}]") + return False + production_rules = self.parsing_table[symbol][word] + if len(production_rules) >= 2: + # print(f"There are multiple rules to convert {symbol} with one word prediction of {word}") + return False + production_rule = production_rules[0] + produced = production_rule[symbol] + if produced != "": + for produced_word in produced[::-1]: + stack.append(produced_word) + return True + +class Solution: + def isValid(self, s: str) -> bool: + parser = Parser() + return parser.parse(s) + +solution = Solution() +print(solution.isValid("}")) \ No newline at end of file diff --git a/step7-3.py b/step7-3.py new file mode 100644 index 0000000..406a9a1 --- /dev/null +++ b/step7-3.py @@ -0,0 +1,129 @@ +class Parser: + def __init__(self): + self.V = ["S", "C"] + self.T = ["(", ")", "{", "}", "[", "]", "e"] + self.P = { + "S" : ["CS","e"], + "C" : ["(S)", "{S}","[S]"] + } + self.S = "S" + self.first_map = {} + self.follow_map = {} + self.parsing_table = {} + self._construct_follow_map() + self._construct_parsing_table() + + def first(self, alpha): + if alpha in self.first_map: + return self.first_map[alpha] + if alpha == "e": + return set(["e"]) + result = set() + for i in range(len(alpha)): + symbol = alpha[i] + if symbol in self.T: + result.add(symbol) + self.first_map[alpha] = result + return result + if symbol not in self.V: + raise ValueError(f"the input word [{alpha}] begins with the character not defined in this language.") + if symbol in self.V: + ith_first_result = set() + for produced in self.P[symbol]: + ith_first_result = ith_first_result.union(self.first(produced)) + if "e" not in ith_first_result: + result = result.union(ith_first_result) + self.first_map[alpha] = result + return result + ith_first_result.remove("e") + result = result.union(ith_first_result) + result.add("e") + self.first_map[alpha] = result + return result + + def _construct_follow_map(self): + self.follow_map[self.S] = set(["$"]) + for production_head, produced_list in self.P.items(): + for produced in produced_list: + for processing_idx, symbol in enumerate(produced): + if symbol in self.T: + continue + if symbol in self.V: + if symbol not in self.follow_map: + self.follow_map[symbol] = set() + remaining_string = produced[processing_idx+1:] + remaining_string_first_result = self.first(remaining_string) + if remaining_string == "" or "e" in remaining_string_first_result: + self.follow_map[production_head] = self.follow_map[production_head].union(self.follow_map[symbol]) + if "e" in remaining_string_first_result: + remaining_string_first_result.remove("e") + self.follow_map[symbol] = self.follow_map[symbol].union(remaining_string_first_result) + return + + def follow(self, A): + if A not in self.V: + raise ValueError(f"the input variable [{A}] is not a variable used in this language") + return self.follow_map[A] + + def _construct_parsing_table(self): + for non_terminal in self.V: + self.parsing_table[non_terminal] = {} + for terminal in self.T + ["$"]: + self.parsing_table[non_terminal][terminal] = [] + for production_head, produced_list in self.P.items(): + for produced in produced_list: + produced_first_result = self.first(produced) + for terminal in produced_first_result: + self.parsing_table[production_head][terminal].append({production_head : produced}) + if "e" in produced_first_result: + head_follow_result = self.follow(production_head) + for terminal in head_follow_result: + self.parsing_table[production_head][terminal].append({production_head : produced}) + # print(self.parsing_table) + return + + def parse(self, input): + initial_input = input + for word in input: + if word not in self.T: + # print(f"word [{word}] is not the terminal used in this language") + return False + input = input + "$" + stack = ["$", self.S] + processing_idx = 0 + while stack[-1] != "$": + # print("cur_stack", stack) + # print("predicting word", processing_idx, input[processing_idx]) + symbol = stack.pop() + word = input[processing_idx] + if symbol == word: + processing_idx += 1 + continue + if symbol in self.T: + # print(f"The given input is not this language") + return False + if not self.parsing_table[symbol][word]: + # print(f"There is no way to parse the symbol [{symbol}] with preceeding word [{word}]") + return False + production_rules = self.parsing_table[symbol][word] + if len(production_rules) >= 2: + # print(f"There are multiple rules to convert {symbol} with one word prediction of {word}") + return False + production_rule = production_rules[0] + produced = production_rule[symbol] + # print("produced", produced, "for symbol", symbol) + if produced != "e": + for produced_word in produced[::-1]: + stack.append(produced_word) + # print("processing id", processing_idx) + if processing_idx < len(initial_input): + return False + return True + +class Solution: + def isValid(self, s: str) -> bool: + parser = Parser() + return parser.parse(s) + +solution = Solution() +print(solution.isValid("()")) \ No newline at end of file diff --git a/step8-3.py b/step8-3.py new file mode 100644 index 0000000..0b1938a --- /dev/null +++ b/step8-3.py @@ -0,0 +1,114 @@ +class Parser: + def __init__(self, terminals, nonterminals, rules, start_symbol): + self.terminals = terminals + self.nonterminals = nonterminals + self.rules = rules + self.start_symbol = start_symbol + self.follow_memo = {} + self.parsing_table = {} + self._construct_parsing_table() + + def first(self, string): + if len(string) == 1: + if string == "#": + return set(["#"]) + if string in self.terminals: + return set([string]) + result = set() + for produced_string in self.rules[string]: + result = result | self.first(produced_string) + return result + + result = set() + for symbol in string: + symbol_first_result = self.first(symbol) + if "#" not in symbol_first_result: + result = result | symbol_first_result + return result + symbol_first_result.remove("#") + result = result | symbol_first_result + result.add("#") + return result + + def follow(self, nonterminal): + if nonterminal not in self.nonterminals: + raise ValueError(f"{nonterminal} is not non-terminal") + if nonterminal in self.follow_memo: + return self.follow_memo[nonterminal] + result = set() if nonterminal != self.start_symbol else set(["$"]) + for production_head, produced_strings in self.rules.items(): + for produced_string in produced_strings: + nonterminal_idx = produced_string.find(nonterminal) + if nonterminal_idx == -1: + continue + while nonterminal_idx != -1: + remaining_string = produced_string[nonterminal_idx + 1:] + if remaining_string: + remaining_string_first_res = self.first(remaining_string) + if "#" not in remaining_string_first_res: + result = result | remaining_string_first_res + else: + production_head_follow_result = self.follow(production_head) if production_head != nonterminal else set() + result = result | production_head_follow_result | (remaining_string_first_res - set(["#"])) + else: + production_head_follow_result = self.follow(production_head) if production_head != nonterminal else set() + result = result | production_head_follow_result + nonterminal_idx = produced_string.find(nonterminal, nonterminal_idx + 1) + self.follow_memo[nonterminal] = result + return result + + def _construct_parsing_table(self): + for nonterminal in self.nonterminals: + self.parsing_table[nonterminal] = {} + for terminal in self.terminals + ["$"]: + self.parsing_table[nonterminal][terminal] = [] + for production_head, produced_strings in self.rules.items(): + for produced_string in produced_strings: + produced_string_first_result = self.first(produced_string) + if "#" in produced_string_first_result: + head_follow_result = self.follow(production_head) + for terminal in head_follow_result: + self.parsing_table[production_head][terminal].append({production_head : produced_string}) + produced_string_first_result.remove("#") + for terminal in produced_string_first_result: + self.parsing_table[production_head][terminal].append({production_head : produced_string}) + # print(self.parsing_table) + return + + def parse(self, input): + end_idx = len(input) + input = input + "$" + stack = ["$", self.start_symbol] + processing_idx = 0 + while stack[-1] != "$": + symbol = stack.pop() + input_char = input[processing_idx] + if symbol == input_char: + processing_idx += 1 + continue + if symbol in self.terminals: + return False + if not self.parsing_table[symbol][input_char]: + return False + production_rules = self.parsing_table[symbol][input_char] + if len(production_rules) >= 2: + return False + production_rule = production_rules[0] + produced_string = production_rule[symbol] + if produced_string != "#": + for produced_char in produced_string[::-1]: + stack.append(produced_char) + return processing_idx == end_idx + +class Solution: + def isValid(self, s: str) -> bool: + + nonterminals = ["S", "C"] + terminals = ["(", ")", "{", "}", "[", "]", "e"] + rules = { + "S" : ["CS","#"], + "C" : ["(S)", "{S}","[S]"] + } + start_symbol = "S" + parser = Parser(terminals, nonterminals, rules, start_symbol) + return parser.parse(s)