-
Notifications
You must be signed in to change notification settings - Fork 0
Solved Arai60/8. String to Integer (atoi) #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| ## Step 1. Initial Solution | ||
|
|
||
| - 色々考えることが多いので順番に分けて処理する | ||
| - いくつか勘違いをしていて時間を取られた | ||
| - 1 << 31 - 1とすると 1 << 30になる | ||
| - if ‘ ‘: とするとTrue扱いになる | ||
| - 整数判定(str.isdigit()) | ||
| - - 1 // 10 は 0ではなく-1を返す | ||
| - もっときれいな書き方はあるだろうが取りあえずの実装 | ||
| - 特段難しいことはない処理でも多段階になるとどうしても作業効率が落ちてしまう | ||
| - どこかに全体設計を書いてから実装した方が良いのだろうか | ||
|
|
||
| ```python | ||
| POSITIVE = 1 | ||
| NEGATIVE = 0 | ||
| MAX_INT = (1 << 31) - 1 | ||
| MIN_INT = -(1 << 31) | ||
| NUMBERS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} | ||
|
|
||
| class Solution: | ||
| def myAtoi(self, s: str) -> int: | ||
| sign = POSITIVE | ||
| index = 0 | ||
| num = 0 | ||
| while index < len(s): | ||
| if s[index] == ' ': | ||
| index += 1 | ||
| continue | ||
| if s[index] == '-': | ||
| sign = NEGATIVE | ||
| index += 1 | ||
| break | ||
| if s[index] == '+': | ||
| index += 1 | ||
| break | ||
| if s[index] in NUMBERS: | ||
| break | ||
| return 0 | ||
|
|
||
| while index < len(s): | ||
| if s[index] == '0': | ||
| index += 1 | ||
| continue | ||
| if s[index] in NUMBERS: | ||
| break | ||
| if s[index]: | ||
| return 0 | ||
|
|
||
| while index < len(s): | ||
| if s[index] in NUMBERS: | ||
| if sign == POSITIVE and (MAX_INT - int(s[index])) // 10 - num < 0: | ||
| return MAX_INT | ||
| if sign == NEGATIVE and (MIN_INT + int(s[index])) // 10 + 1 + num > 0: | ||
| return MIN_INT | ||
| num = num * 10 + int(s[index]) | ||
| index += 1 | ||
| else: | ||
| break | ||
| return num if sign == POSITIVE else -num | ||
| ``` | ||
|
|
||
| ### Complexity Analysis | ||
|
|
||
| - 時間計算量:O(N) | ||
| - 空間計算量:O(1) | ||
|
|
||
| ## Step 2. Alternatives | ||
|
|
||
| - まずは自分でリファクタリング | ||
| - 少しは見やすくなったがやはり複雑に見える | ||
|
|
||
| ```python | ||
| MAX_INT = (1 << 31) - 1 | ||
| MIN_INT = -(1 << 31) | ||
|
|
||
| class Solution: | ||
| def myAtoi(self, s: str) -> int: | ||
| index = 0 | ||
| while index < len(s): | ||
| if s[index] == ' ': | ||
| index += 1 | ||
| continue | ||
| break | ||
|
|
||
| is_positive = True | ||
| if index == len(s): | ||
| return 0 | ||
| if s[index] == '+': | ||
| index += 1 | ||
| elif s[index] == '-': | ||
| index += 1 | ||
| is_positive = False | ||
|
|
||
| num = 0 | ||
| while index < len(s) and s[index].isdigit(): | ||
| if is_positive and num > (MAX_INT - int(s[index])) // 10: | ||
| return MAX_INT | ||
| if not is_positive and -num < (MIN_INT + int(s[index])) // 10 + 1: | ||
| return MIN_INT | ||
| num = num * 10 + int(s[index]) | ||
| index += 1 | ||
| return num if is_positive else -num | ||
| ``` | ||
|
|
||
| - https://github.com/tokuhirat/LeetCode/pull/59/files#diff-ed1cf3d46cfd4bddc1808bc5157b0cc7596de0a5858c3d1da82c0d1b1006874cR13-R29 | ||
| - 細かい処理をメソッドとして定義している | ||
| - 多少読みやすいが全体として長くは見える | ||
| - https://github.com/tokuhirat/LeetCode/pull/59/files#diff-ed1cf3d46cfd4bddc1808bc5157b0cc7596de0a5858c3d1da82c0d1b1006874cR79 | ||
| - 一桁のintはcase文くらいしか思いついていなかったが確かにordでも良い | ||
| - str.isdigit()についてもちゃんと理解していなかった | ||
| - `Digits include decimal characters and digits that need special handling, such as the compatibility superscript digits` | ||
| - https://github.com/tokuhirat/LeetCode/pull/59/files#diff-ed1cf3d46cfd4bddc1808bc5157b0cc7596de0a5858c3d1da82c0d1b1006874cR92 | ||
| - シフト演算子の優先順位が低いことはドキュメントにもあるらしい、盲点だった | ||
| - https://github.com/naoto-iwase/leetcode/pull/60/files#diff-663ed9c661571932db8d4d60eac1099f01e58022328775ad01b28bab535523e8R335 | ||
| - 最後の計算部分はvalueの方が良い名前だと感じた | ||
| - 先にsignを入れながら計算するのもこの形なら綺麗 | ||
| - https://github.com/naoto-iwase/leetcode/pull/60/files#diff-663ed9c661571932db8d4d60eac1099f01e58022328775ad01b28bab535523e8R278 | ||
| - このように正規表現を綺麗に書くのが一番簡潔 | ||
| - ただ、コメントにもある通り認知負荷は上がるので読む時間が短くなるかは微妙 | ||
| - 一か所を見て全体のルールを把握できるのは利点 | ||
| - `_PATTERN = re.compile(r"\s*([+-]?)(\d+)")` | ||
| - \s*: スペース0個以上 | ||
| - ([+-]?): +-のいずれか任意でグループ化 | ||
| - (\d+): 0-9が一つ以上 | ||
| - たまに書かないと忘れるので良い復習になった | ||
| - 少し間が空いてから再度書いたらまたかなり時間がかかった | ||
| - 関数化せずにそのまま書いてしまった方が読みやすい気はする | ||
|
|
||
| ```python | ||
| MAX_INT = (1 << 31) - 1 | ||
| MIN_INT = -(1 << 31) | ||
| class Solution: | ||
| def myAtoi(self, s: str) -> int: | ||
| def skip_whitespace() -> None: | ||
| if s[self.index] == ' ': | ||
| self.index += 1 | ||
| return | ||
| self.checked_whitespace = True | ||
|
|
||
| def get_sign() -> None: | ||
| if s[self.index] == '+': | ||
| self.sign = 1 | ||
| self.index += 1 | ||
| elif s[self.index] == '-': | ||
| self.sign = -1 | ||
| self.index += 1 | ||
| self.checked_sign = True | ||
|
|
||
| def skip_zero() -> None: | ||
| if s[self.index] == '0': | ||
| self.index += 1 | ||
| elif s[self.index] in string.digits: | ||
| self.checked_zero = True | ||
| else: | ||
| self.index = len(s) | ||
|
|
||
| def round_int() -> None: | ||
| if s[self.index] not in string.digits: | ||
| self.index = len(s) | ||
| return | ||
| if self.num > (MAX_INT - ch_to_i(s[self.index])) // 10: | ||
| self.num = MAX_INT | ||
| self.index = len(s) | ||
| return | ||
| elif self.num < (MIN_INT + ch_to_i(s[self.index])) // 10 + 1: | ||
| self.num = MIN_INT | ||
| self.index = len(s) | ||
| return | ||
| self.num = self.num * 10 + self.sign * ch_to_i(s[self.index]) | ||
| self.index += 1 | ||
|
|
||
| def ch_to_i(ch: str) -> int: | ||
| return ord(ch) - ord('0') | ||
|
|
||
| self.index = 0 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. インスタンス変数を変更すると、複数のスレッドで同時にこの関数を読んだときに競合状態が起こると思います。現実的なコストで避けられる場合は、避けたほうが無難だと思います。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ステートマシン風に書きたかったのかなと思いました。以下のような書き方もあります。 import enum
class State(enum.Enum):
WHITESPACE = 1
SIGN = 2
ZERO = 3
INT = 4
END = 5
MAX_INT = (1 << 31) - 1
MIN_INT = -(1 << 31)
class Solution:
def myAtoi(self, s: str) -> int:
def parse_whitespace(index: int) -> tuple[State, int]:
if s[index] == ' ':
return State.WHITESPACE, index + 1
return State.SIGN, index
def parse_sign(index: int) -> tuple[State, int, int]:
if s[index] == '+':
return State.ZERO, index + 1, 1
if s[index] == '-':
return State.ZERO, index + 1, -1
return State.ZERO, index, 1
def parse_zero(index) -> tuple[State, int]:
if s[index] == '0':
return State.ZERO, index + 1
if s[index] in string.digits:
return State.INT, index
return State.END, len(s)
def parse_int(index, sign, num) -> tuple[State, int, int]:
if s[index] not in string.digits:
return State.END, len(s), num
if num > (MAX_INT - ch_to_i(s[index])) // 10:
return State.END, len(s), MAX_INT
if num < (MIN_INT + ch_to_i(s[index])) // 10 + 1:
return State.END, len(s), MIN_INT
return State.INT, index + 1, num * 10 + sign * ch_to_i(s[index])
def ch_to_i(ch: str) -> int:
return ord(ch) - ord('0')
index = 0
state = State.WHITESPACE
sign = 1
num = 0
while index < len(s):
match state:
case State.WHITESPACE:
state, index = parse_whitespace(index)
case State.SIGN:
state, index, sign = parse_sign(index)
case State.ZERO:
state, index = parse_zero(index)
case State.INT:
state, index, num = parse_int(index, sign, num)
case State.END:
break
return num
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 確かにこういう書き方もありですね。 |
||
| self.checked_whitespace = False | ||
| self.checked_sign = False | ||
| self.checked_zeros = False | ||
| self.sign = 1 | ||
| self.num = 0 | ||
| while self.index < len(s): | ||
| if not self.checked_whitespace: | ||
| skip_whitespace() | ||
| continue | ||
| if not self.checked_sign: | ||
| get_sign() | ||
| continue | ||
| if not self.checked_zero: | ||
| skip_zero() | ||
| continue | ||
| round_int() | ||
| return self.num | ||
| ``` | ||
|
|
||
|
|
||
| ## Step 3. Final Solution | ||
|
|
||
| - 分かりやすさを失わない範囲でなるべくコンパクトに書くように意識 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def myAtoi(self, s: str) -> int: | ||
| index = 0 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if s is None:
return 0Noneの時の処理を入れたくなりました |
||
| while index < len(s): | ||
| if s[index] != ' ': | ||
| break | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| index += 1 | ||
| if index >= len(s): | ||
| return 0 | ||
|
|
||
| sign = 1 | ||
| if s[index] == '+': | ||
| index += 1 | ||
| elif s[index] == '-': | ||
| index += 1 | ||
| sign = -1 | ||
|
|
||
| MAX_INT = (1 << 31) - 1 | ||
| MIN_INT = -(1 << 31) | ||
| num = 0 | ||
| while index < len(s): | ||
| if s[index] not in string.digits: | ||
| return num | ||
| next_digit = sign * (ord(s[index]) - ord('0')) | ||
| if num > (MAX_INT - next_digit) // 10: | ||
| return MAX_INT | ||
| if num < (MIN_INT - next_digit) // 10 + 1: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if num <= (MIN_INT - next_digit) // 10:でも動きますかね?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そうですね。ただ、自分の意図としては上と同じように超えた時に丸めるという意味合いが重要と思っているのでこうしてます |
||
| return MIN_INT | ||
| num = num * 10 + next_digit | ||
| index += 1 | ||
| return num | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python では int は多倍長整数で実装されているため、 32/64-bit のオーバーフローしません。そのため、計算してから範囲を超えたかどうかを判定しても大丈夫です。そのほうがコードがシンプルになると思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
もちろん承知の上ですが、その処理をして欲しい問題なのかなと思って書きました
(MAX_INTの定義で越えてるという話はありそうですが)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
他の(オーバーフローが)問題になるような言語だと、標準ライブラリーにそういう関数があることが多いというのも意識してもいいかもしれません。
まあ、要は自分で作らないべきものであるということです。