diff --git a/8. String to Integer (atoi)/8. String to Integer (atoi).md b/8. String to Integer (atoi)/8. String to Integer (atoi).md new file mode 100644 index 0000000..b32f95a --- /dev/null +++ b/8. String to Integer (atoi)/8. String to Integer (atoi).md @@ -0,0 +1,256 @@ +# 8. String to Integer (atoi) +## STEP1 +- 何も見ずに解いてみる +- 言われていることを実装すれば良いが細かいところで引っかかった。 +- 空白判定は要件としては" "と比較すれば良いが、string.whitespace を使う方がよい場面もありそう。 +- Rounding は Python だと簡単。C++だとどうするんでしょう。 +- _read_integer に index = len(s) を入れても大丈夫だが、事前にチェックした方がわかりやすいかもしれない。 +- 各メソッドに s を渡しているのは微妙かもしれない。len(s) の方がよい? +```python +import string + + +class Solution: + def myAtoi(self, s: str) -> int: + index = 0 + # Ignore any leading whitespace + index = self._skip_white_space(s, index) + if index == len(s): + return 0 + + # Determine the sign + is_positive, index = self._read_sign(s, index) + + # Read the integer + integer = self._read_integer(s, index) + + # Rounding + rounded_integer = self._rounding_to_32bit_signed_integer(integer, is_positive) + return rounded_integer + + def _skip_white_space(self, s, start: int) -> int: + for i in range(start, len(s)): + if s[i] != " ": + return i + return len(s) + + def _read_sign(self, s, index: int) -> tuple[bool, int]: + if s[index] == "+": + return True, index + 1 + if s[index] == "-": + return False, index + 1 + return True, index + + def _read_integer(self, s, index: int) -> int: + result = 0 + while index < len(s): + if s[index] != "0": + break + index += 1 + else: + return 0 + + for i in range(index, len(s)): + if s[i] in string.digits: + result = 10 * result + int(s[i]) + continue + break # non digit exists + return result + + def _rounding_to_32bit_signed_integer(self, integer: int, is_positive: bool) -> int: + MAX_INT = (1 << 31) - 1 + MIN_INT = -(1 << 31) + print(integer) + if is_positive: + if integer > MAX_INT: + return MAX_INT + return integer + else: + integer *= -1 + if integer < MIN_INT: + return MIN_INT + return integer +``` + +## STEP2 +### プルリクやドキュメントを参照 +- https://github.com/olsen-blue/Arai60/pull/60/files + - 各処理を関数化した方がわかりやすく感じる (下で少し思い直しました)。追加の要件が来たらべた書きの場合対応が難しそう。ただ上の STEP1 で書いた index と s を関数間で取り回しているコードがわかりやすいかは微妙。関数内関数でも良いが読みにくさはありそう。 + - 一桁の数字に対しても int を使わないことが想定されているかも。ord で変換できる。 + - https://docs.python.org/3/library/functions.html#ord + > If the argument is a one-character string, return the Unicode code point of that character. + - https://docs.python.org/3/library/stdtypes.html#str.isdigit + - 想定範囲より広い概念の数字をTrueと判定する。 +- MAX_INT = (1 << 31) - 1 は MAX_INT = 0x7FFF_FFFF でも良いかも。MIN_INT = -MAX_INT - 1 +- https://docs.python.org/3/library/functions.html#divmod + - a % b は 0 でない場合 b と同符号 +- オーバーフロー判定で MAX_INT, MIN_INT が 10 の倍数である場合にも対応しようとすると複雑。対応する必要があるのか。この関数は基盤になりそうだからどんな入力でも正しく動いてほしいとは思う。 +- https://github.com/fhiyo/leetcode/pull/57/files#r1730005383 + - 拡張性について。hex や oct への拡張は結構大変そう。 + - string.hexdigits, string.octdigits はある。 +- https://docs.python.org/3/reference/expressions.html#operator-precedence + - シフト演算子の優先順位は +, - より低い +```python +import string + + +MAX_INT = 0x7FFF_FFFF +MIN_INT = -MAX_INT - 1 + + +class Solution: + def myAtoi(self, s: str) -> int: + index = 0 + # Ignore any leading whitespace + index = self._skip_whitespace(s, index) + if index == len(s): + return 0 + + # Determine the sign + sign, index = self._parse_sign(s, index) + + # Read the integer, rounding on overflow. + integer = self._parse_integer(s, index, sign) + return integer + + def _skip_whitespace(self, s, index: int) -> int: + for i in range(index, len(s)): + if s[i] != " ": + return i + return len(s) + + def _parse_sign(self, s, index: int) -> tuple[int, int]: + if s[index] == "+": + return 1, index + 1 + if s[index] == "-": + return -1, index + 1 + return 1, index + + def _parse_integer(self, s, index: int, sign: int) -> int: + value = 0 + for i in range(index, len(s)): + if s[i] not in string.digits: + break # non digit exists + digit = ord(s[i]) - ord("0") + if self._is_overflow(sign, value, digit): + return MAX_INT if sign == 1 else MIN_INT + value = 10 * value + digit * sign + return value + + def _is_overflow(self, sign: int, value: int, digit: int) -> bool: + if sign == 1: + if value > MAX_INT // 10: + return True + if value == MAX_INT // 10 and digit > MAX_INT % 10: + return True + else: + if value < (MIN_INT + 9) // 10: + return True + if value == (MIN_INT + 9) // 10 and digit > (10 - MIN_INT % 10) % 10: + return True + return False +``` +- あまり関数化せずに書き直した。これでも意外とわかりやすい。 +```python +import string + + +MAX_INT = 0x7FFF_FFFF +MIN_INT = -MAX_INT - 1 + + +class Solution: + def myAtoi(self, s: str) -> int: + index = 0 + # Ignore any leading whitespace + while index < len(s) and s[index] == " ": + index += 1 + if index == len(s): + return 0 + + # Determine the sign + sign = 1 + if s[index] == "+": + index += 1 + elif s[index] == "-": + sign = -1 + index += 1 + + # Read the integer, rounding on overflow. + value = 0 + for i in range(index, len(s)): + if s[i] not in string.digits: + break # non digits exists + digit = ord(s[i]) - ord("0") + if self._is_overflow(sign, value, digit): + return MAX_INT if sign == 1 else MIN_INT + value = 10 * value + digit * sign + return value + + def _is_overflow(self, sign: int, value: int, digit: int) -> bool: + if sign == 1: + if value > MAX_INT // 10: + return True + if value == MAX_INT // 10 and digit > MAX_INT % 10: + return True + else: + if value < (MIN_INT + 9) // 10: + return True + if value == (MIN_INT + 9) // 10 and digit > (10 - MIN_INT % 10) % 10: + return True + return False +``` +## STEP3 +### 3回ミスなく書く +```python +import string + + +MAX_INT = 0x7FFF_FFFF +MIN_INT = -MAX_INT - 1 + + +class Solution: + def myAtoi(self, s: str) -> int: + index = 0 + # Skip whitespace + while index < len(s) and s[index] == " ": + index += 1 + if index == len(s): + return 0 + + # Determine the sign + sign = 1 + if s[index] == "+": + index += 1 + elif s[index] == "-": + sign = -1 + index += 1 + + # Read the integer + value = 0 + while index < len(s): + if s[index] not in string.digits: + break + digit = ord(s[index]) - ord("0") + if self._is_overflow(sign, value, digit): + return MAX_INT if sign == 1 else MIN_INT + value = 10 * value + digit * sign + index += 1 + return value + + def _is_overflow(self, sign: int, value: int, digit: int) -> bool: + if sign == 1: + if value > MAX_INT // 10: + return True + if value == MAX_INT // 10 and digit > MAX_INT % 10: + return True + else: + if value < (MIN_INT + 9) // 10: + return True + if value == (MIN_INT + 9) // 10 and digit > (10 - MIN_INT % 10) % 10: + return True + return False +``` + +7分,6分,5分で3回Accept