Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions arai60/word_break/phase1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 配列を使ったdp
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
is_word_break = [False for _ in range(len(s))]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[False] * len(s)のほうが書くの楽なのではないかなと思いました。(好みの問題かもしれないです)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

実行時間の方もそちらの方が早いので, mutableなものを要素に持つもの以外はこんどからそちらで書きますhttps://qiita.com/Krypf/items/5efb681d06e1ebd2abab

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_word_break は動詞の原形から始まっているため、関数名に見えます。 breakable または tokenizable あたりが良いと思います。個人的には、複数の値を持つ変数の名前には複数形の s を付けるのですが、これは好みの問題かもしれません。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。確かに関数名に見えますね

# is_word_break means s[:i+1] can be broken.
for i, c in enumerate(s):
string_from_beginning = s[:i+1]
for word in wordDict:
if string_from_beginning == word:
is_word_break[i] = True
elif i+1-len(word) >= 0 and s[i+1-len(word):i+1] == word and is_word_break[i-len(word)]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

二項演算子の両側にスペースを空けることをお勧めいたします。ただし、スライスの中の式の二項演算子の両側は、両側にスペースを空けないほうが良いと思います。

https://peps.python.org/pep-0008/#other-recommendations

Always surround these binary operators with a single space on either side: assignment (=), augmented assignment (+=, -= etc.), comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), Booleans (and, or, not).

However, in a slice the colon acts like a binary operator, and should have equal amounts on either side (treating it as the operator with the lowest priority). In an extended slice, both colons must have the same amount of spacing applied. Exception: when a slice parameter is omitted, the space is omitted:

https://google.github.io/styleguide/pyguide.html#s3.6-whitespace

Surround binary operators with a single space on either side for assignment (=), comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), and Booleans (and, or, not). Use your better judgment for the insertion of spaces around arithmetic operators (+, -, *, /, //, %, **, @).

is_word_break[i] = True
return is_word_break[len(s)-1]

# 再起的なdp
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
is_word_break = [False for _ in range(len(s))]
seen = [False for _ in range(len(s))]
def recursiveWordBreak(index, seen, is_word_break):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

関数名は原則動詞の原形から始まることが多いと思います。 break_word_recursively() でしょうか。また、 recursively はソースコードを読めば分かるため、省略して、 break_word() 良いと思います。

if index >= len(s):
return
if seen[index]:
return
string_from_beginning = s[:index+1]
for word in wordDict:
if string_from_beginning == word:
seen[index] = True
is_word_break[index] = True
return recursiveWordBreak(index+1, seen, is_word_break)
elif index+1-len(word) >= 0 and word == s[index+1-len(word):index+1] and is_word_break[index-len(word)]:
seen[index] = True
is_word_break[index] = True
return recursiveWordBreak(index+1, seen, is_word_break)
seen[index] = True
return recursiveWordBreak(index+1, seen, is_word_break)
recursiveWordBreak(0, seen, is_word_break)
return is_word_break[len(s)-1]

# タイムアウトしたコード, 本質的にはメモしてない再帰のように考える探索木が爆発してしまったと考えられる。
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
char_to_string = defaultdict(list)
for string in wordDict:
char_to_string[string[0]].append(string)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この高速化はあんまり効果がない気がします。

startswith 使って start を指定すればスライスを取らなくなるので十分でしょう。
https://docs.python.org/3/library/stdtypes.html#str.startswith

stack = [0]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seen = set()
として、

if cursor in seen:
    continue
seen.add(cursor)

とでもすればいいでしょう。

ああ、下の方で bool 配列使ってますね。私は set のほうが素直で好きですが、趣味の範囲でしょう。

while stack:
cursor = stack.pop()
if cursor >= len(s):
return True

beginning_char = s[cursor]
for string in char_to_string[beginning_char]:
if s[cursor:cursor+len(string)] in char_to_string[beginning_char]:
stack.append(cursor+len(string))
return False
43 changes: 43 additions & 0 deletions arai60/word_break/phase2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Reference
shining-aiさん: https://github.com/shining-ai/leetcode/pull/39/files
自分も書いていてs[index+1-len(word):index+1]は時間計算量的にどうなのか気になった。ローリングハッシュで書き換えることを検討する。今回の問題はtop-downの方が書きやすかった...?
hayashi-ayさん: https://github.com/hayashi-ay/leetcode/pull/61/files
Exzrgさん: https://github.com/Exzrgs/LeetCode/pull/10/files startwithという方法もpythonにあることを学んだ。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startswithですね。
細かいんですが、割とtypoするので。
https://docs.python.org/ja/3/library/stdtypes.html#str.startswith

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かにそうですね...


結局最速はwordDict, sのローリングハッシュを計算してstoreしておくことになりそう。
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

いや、これローリングハッシュで書いてきたら、信用できない感じがするので、プロダクションコードに入れないでくれという気持ちになりそうです。

re2 だったらいいんじゃないでしょうか。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm
Aho–Corasick algorithm
はありますが、文字列アルゴリズムは大変なものが多いです。
fhiyo さんが最近書いていました。
fhiyo/leetcode@ed3ace0

"""

# @cacheで覚えておいて再帰 参考: shining-aiさん
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
@cache
def is_segmented(start):
if start == len(s):
return True
for word in wordDict:
if s[start:start+len(word)] != word:
continue
elif is_segmented(start+len(word)):
return True
return False
return is_segmented(0)

# @cacheを使わずにメモ化
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
seen = [False for _ in range(len(s))]
is_tokenized = [False for _ in range(len(s))]
def check_tokenizable(start, seen, is_tokenized):
if start == len(s):
return
if seen[start]:
return
seen[start] = True
for word in wordDict:
if s.startswith(word, start):
is_tokenized[start+len(word)-1] = True
check_tokenizable(start+len(word), seen, is_tokenized)
return
check_tokenizable(0, seen, is_tokenized)
return is_tokenized[-1]
17 changes: 17 additions & 0 deletions arai60/word_break/phase3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
seen = [False for _ in range(len(s))]
is_tokanizable = [False for _ in range(len(s))]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本質的ではないですが、tokenizeをtypoしていると思います。
個人的には、can_tokenizeのほうが短くて良いのかなと思いました

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tokenizable[形]があるかと思いましたがどうやらなかったようです。can_tokenizeにいたします

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

というよりは、tokanizableになってしまっているという指摘でした。
tokenizableがないのは驚きですね...。とはいえ個人的には、造語として使っちゃってもよいのではないかと思ってます(プログラミングでは英語の文法を無視した命名がされることがあり、Goの標準ライブラリとかでもちらほら見かけます)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ, 本当だ。名前typoしてる

Comment on lines +3 to +4
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seenis_tokanizableを関数の引数に持ってくるのであれば、宣言は関数を使う直前のほうが良いのではないかと思いました。
もしくは、関数の引数にせずにアクセスするというのも手だと思います。

def check_tokanizable(start, seen, is_tokanizable):
if start >= len(s):
return
if seen[start]:
return
seen[start] = True
for word in wordDict:
if s.startswith(word, start):
is_tokanizable[start+len(word)-1] = True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この部分ですでにTrueかどうかの判定を入れることで、seenを使わずに重複してチェックしないことを実現できそうです。
if s.startswith(word, start) and not is_tokanizable[start+len(word)-1]:みたいな感じですね

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ嘘でした。調べた結果がFalseの場合がありますね。
初期値をNoneにして最後にif not True: Falseとかの処理を入れたら実現できますね。
とはいえこっちのほうが良いのか?というのはわからないです...

check_tokanizable(start+len(word), seen, is_tokanizable)
return
check_tokanizable(0, seen, is_tokanizable)
return is_tokanizable[-1]
17 changes: 17 additions & 0 deletions arai60/word_break/phase4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
seen = [False] * len(s)
is_tokenizable = [False] * len(s)
def check_tokenizable(start):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には、@cache使ったStep2のほうがわかりやすいかなと思いました。
check_tokenizable(start)という関数名だと、startというindexを入れたら、そのindexから始める文字列がtokenizableかどうかをbool値で返す関数を想像してしまいそうです。

if start >= len(s):
return
if seen[start]:
return
seen[start] = True
for word in wordDict:
if s.startswith(word, start):
is_tokenizable[start+len(word)-1] = True
check_tokenizable(start+len(word))
return
check_tokenizable(0)
return is_tokenizable[-1]