From 0039b0490463225e88900dc49cc5fe9ae9b482f6 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Fri, 13 Feb 2026 21:54:02 +0900 Subject: [PATCH 01/15] step1 --- memo.md | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ step1-1.py | 24 ++++++++++++++ step1-2.py | 20 ++++++++++++ step1-3.py | 23 +++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 step1-1.py create mode 100644 step1-2.py create mode 100644 step1-3.py diff --git a/memo.md b/memo.md index 4bd0397..e514f52 100644 --- a/memo.md +++ b/memo.md @@ -1 +1,96 @@ # 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 (再帰) + +```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, []) +``` \ 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 From 4b8937c2815ea7a2b0eb584da36e3534c69639d4 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Fri, 13 Feb 2026 21:59:26 +0900 Subject: [PATCH 02/15] step2 --- memo.md | 32 ++++++++++++++++++++++++++++++++ step2-2.py | 22 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 step2-2.py diff --git a/memo.md b/memo.md index e514f52..43a3eaf 100644 --- a/memo.md +++ b/memo.md @@ -69,6 +69,9 @@ class Solution: ## Code3 (再帰) +* `return True`や`return False`が交互に出現するのが気持ち悪かったので, `return True`が最後に登場するようにした. +* 途中計算に使う値を変数に切り出して読みやすくした + ```python BRACKET_PAIRS = { "(" : ")", @@ -93,4 +96,33 @@ class Solution: 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 ``` \ 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 From e3985e51526f71b84e067ae2e5b515ec2b822747 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Fri, 13 Feb 2026 22:02:23 +0900 Subject: [PATCH 03/15] =?UTF-8?q?step3=201=E5=9B=9E=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- step3-2.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 step3-2.py diff --git a/step3-2.py b/step3-2.py new file mode 100644 index 0000000..96eef76 --- /dev/null +++ b/step3-2.py @@ -0,0 +1,23 @@ +class Solution: + def isValid(self, s: str) -> bool: + unclosed_open_brackets = [] + bracket_pairs = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brackets = bracket_pairs.keys() + for i in range(len(s)): + if s[i] in open_brackets: + unclosed_open_brackets.append(s[i]) + continue + if len(unclosed_open_brackets) == 0: + return False + last_unclosed_open_bracket = unclosed_open_brackets.pop(-1) + expected_close_bracket = bracket_pairs[last_unclosed_open_bracket] + if s[i] != expected_close_bracket: + return False + if len(unclosed_open_brackets) > 0: + return False + return True + \ No newline at end of file From 5fdd17ed795e9ece1eab77a425f3e7b41f31454f Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Fri, 13 Feb 2026 22:04:50 +0900 Subject: [PATCH 04/15] =?UTF-8?q?step3=20=EF=BC=92=E5=9B=9E=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- step3-2.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/step3-2.py b/step3-2.py index 96eef76..1482111 100644 --- a/step3-2.py +++ b/step3-2.py @@ -13,11 +13,10 @@ def isValid(self, s: str) -> bool: continue if len(unclosed_open_brackets) == 0: return False - last_unclosed_open_bracket = unclosed_open_brackets.pop(-1) - expected_close_bracket = bracket_pairs[last_unclosed_open_bracket] + 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 - \ No newline at end of file + return True \ No newline at end of file From 122047e35250e5eb8111098967d2a3f1a51ac259 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Fri, 13 Feb 2026 22:06:44 +0900 Subject: [PATCH 05/15] =?UTF-8?q?step3=203=E5=9B=9E=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- step3-2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/step3-2.py b/step3-2.py index 1482111..adcd0b9 100644 --- a/step3-2.py +++ b/step3-2.py @@ -6,9 +6,9 @@ def isValid(self, s: str) -> bool: "{" : "}", "[" : "]" } - open_brackets = bracket_pairs.keys() + open_brakets = bracket_pairs.keys() for i in range(len(s)): - if s[i] in open_brackets: + if s[i] in open_brakets: unclosed_open_brackets.append(s[i]) continue if len(unclosed_open_brackets) == 0: @@ -19,4 +19,4 @@ def isValid(self, s: str) -> bool: return False if len(unclosed_open_brackets) > 0: return False - return True \ No newline at end of file + return True From 03ebdab7fcf48aeae6f8df6379d5bc6fb5a04e3a Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Fri, 13 Feb 2026 22:07:12 +0900 Subject: [PATCH 06/15] step3 comment --- memo.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/memo.md b/memo.md index 43a3eaf..151a46b 100644 --- a/memo.md +++ b/memo.md @@ -125,4 +125,34 @@ class Solution: 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 + ``` \ No newline at end of file From fe35011bb5efbfec2bec71ba22f85890a50c82ff Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sat, 14 Feb 2026 00:02:55 +0900 Subject: [PATCH 07/15] step4 --- step4-3.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 step4-3.py 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 From ecb2984a1677ce293597a57b94fa67e2f35cd20e Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sat, 14 Feb 2026 00:10:53 +0900 Subject: [PATCH 08/15] =?UTF-8?q?step5=201=E5=9B=9E=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- step5-3.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 step5-3.py diff --git a/step5-3.py b/step5-3.py new file mode 100644 index 0000000..56b465c --- /dev/null +++ b/step5-3.py @@ -0,0 +1,33 @@ +class Solution: + def isValid(self, s: str) -> bool: + open_to_close = { + "(" : ")", + "{" : "}", + "[" : "]" + } + open_brackets = open_to_close.keys() + processing_idx = 0 + + def is_chunk_valid(): + nonlocal processing_idx + if processing_idx >= len(s): + return False + 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 is_chunk_valid(): + return False + if processing_idx >= len(s): + return False + processing_idx += 1 + return True + + while processing_idx < len(s): + if not is_chunk_valid(): + return False + + return True + + \ No newline at end of file From 388a3d273ae1dda6b3d1864670ee5d09212aa790 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sat, 14 Feb 2026 00:14:40 +0900 Subject: [PATCH 09/15] =?UTF-8?q?step5=202=E5=9B=9E=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- step5-3.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/step5-3.py b/step5-3.py index 56b465c..e1ec3e8 100644 --- a/step5-3.py +++ b/step5-3.py @@ -8,26 +8,22 @@ def isValid(self, s: str) -> bool: open_brackets = open_to_close.keys() processing_idx = 0 - def is_chunk_valid(): + def check_chunk_valid(): nonlocal processing_idx - if processing_idx >= len(s): - return False 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 is_chunk_valid(): + 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 is_chunk_valid(): + if not check_chunk_valid(): return False - return True - - \ No newline at end of file + return True \ No newline at end of file From ad8cffb9d188931f89ca67ed3566d3917b4c9a0e Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sat, 14 Feb 2026 00:21:28 +0900 Subject: [PATCH 10/15] md --- memo.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/memo.md b/memo.md index 151a46b..5194dac 100644 --- a/memo.md +++ b/memo.md @@ -155,4 +155,60 @@ class Solution: 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 ``` \ No newline at end of file From e40a809f9b9d9ee1a44e91da69eff707e30ab66a Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sat, 14 Feb 2026 22:52:52 +0900 Subject: [PATCH 11/15] step6 --- step6-3.py | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 step6-3.py diff --git a/step6-3.py b/step6-3.py new file mode 100644 index 0000000..3bdc6d5 --- /dev/null +++ b/step6-3.py @@ -0,0 +1,122 @@ +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 From c904ba3c0f85e05557085955a8995b2cb2b89c25 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sat, 14 Feb 2026 22:53:30 +0900 Subject: [PATCH 12/15] md --- memo.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/memo.md b/memo.md index 5194dac..93d7920 100644 --- a/memo.md +++ b/memo.md @@ -211,4 +211,129 @@ class Solution: return False return True +``` + +# Step6 (LL(1)を実装中だが, バグとりに苦戦中) + +```python +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) + ``` \ No newline at end of file From 1bfa22781867c08b5ee92a75418e19e487e940d2 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sun, 15 Feb 2026 11:09:28 +0900 Subject: [PATCH 13/15] step7 bug fixed for LL(1) --- step6-3.py | 1 + step7-3.py | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 step7-3.py diff --git a/step6-3.py b/step6-3.py index 3bdc6d5..c3b7f41 100644 --- a/step6-3.py +++ b/step6-3.py @@ -1,3 +1,4 @@ +# バグありコード. Step7で修正 class Parser: def __init__(self): self.V = ["S", "C"] 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 From be57ba43de7b5bc7714902d2955fe96f12e35847 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sun, 15 Feb 2026 14:22:52 +0900 Subject: [PATCH 14/15] step8 md --- memo.md | 186 +++++++++++++++++++++++++++-------------------------- step8-3.py | 114 ++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 91 deletions(-) create mode 100644 step8-3.py diff --git a/memo.md b/memo.md index 93d7920..858cc55 100644 --- a/memo.md +++ b/memo.md @@ -213,127 +213,131 @@ class Solution: return True ``` -# Step6 (LL(1)を実装中だが, バグとりに苦戦中) +# 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): - self.V = ["S", "C"] - self.T = ["(", ")", "{", "}", "[", "]", ""] - self.P = { - "S" : ["CS",""], - "C" : ["(S)", "{S}","[S]"] - } - self.S = "S" - self.first_map = {} - self.follow_map = {} + 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_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([""]) + 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 i in range(len(alpha)): - symbol = alpha[i] - if symbol in self.T: - result.add(symbol) - self.first_map[alpha] = result + for symbol in string: + symbol_first_result = self.first(symbol) + if "#" not in symbol_first_result: + result = result | symbol_first_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 + symbol_first_result.remove("#") + result = result | symbol_first_result + result.add("#") 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 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 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: + 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}) - print(self.parsing_table) + 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): - for word in input: - if word not in self.T: - # print(f"word [{word}] is not the terminal used in this language") - return False + end_idx = len(input) input = input + "$" - stack = ["$", self.S] + stack = ["$", self.start_symbol] processing_idx = 0 while stack[-1] != "$": symbol = stack.pop() - word = input[processing_idx] - if symbol == word: + input_char = input[processing_idx] + if symbol == input_char: processing_idx += 1 continue - if symbol in self.T: - # print(f"The given input is not this language") + if symbol in self.terminals: return False - if not self.parsing_table[symbol][word]: - # print(f"There is no way to parse the symbol [{symbol}] with preceeding word [{word}]") + if not self.parsing_table[symbol][input_char]: return False - production_rules = self.parsing_table[symbol][word] + production_rules = self.parsing_table[symbol][input_char] 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 + 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: - parser = Parser() + + 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/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) From 4580d131d6b72147fe593a78862a1f689d7bff32 Mon Sep 17 00:00:00 2001 From: Kazuki Kitano Date: Sun, 15 Feb 2026 14:23:36 +0900 Subject: [PATCH 15/15] =?UTF-8?q?=E8=AA=A4=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- memo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/memo.md b/memo.md index 858cc55..4471e2d 100644 --- a/memo.md +++ b/memo.md @@ -217,7 +217,7 @@ class Solution: * LL(1)の実装をした. * アルゴリズムとかは[ブログ](https://kazuki.jp.net/archives/456)にまとめた. -* `follow`で循環が起きているとバグりそうだけど, うまいこと対照する方法が思いつかなかった. +* `follow`で循環が起きているとバグりそうだけど, うまいこと対処する方法が思いつかなかった. * A -> aBC * first(C)が"#"を含む時, follow(A)を計算して, follow(B)に追加する * B -> aAC