Skip to content
Open
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
256 changes: 256 additions & 0 deletions 8. String to Integer (atoi)/8. String to Integer (atoi).md
Original file line number Diff line number Diff line change
@@ -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:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私ならメソッド名は _convert_to_integer にします。

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])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

今回の問題の出題意図は int(s[i]) を自前で実装することだと思いますね。(自戒を込めてですが)

continue
break # non digit exists
return result

def _rounding_to_32bit_signed_integer(self, integer: int, is_positive: bool) -> int:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

個人的には動名詞をメソッド名に使うのは違和感があります。私なら _round_to_32bit_signed_int にします。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

確かに、_round_とした方が良いですね。コメントありがとうございます。

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
Comment on lines +219 to +220
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Read the integer" のところは while の条件でどうせ拾われるので、sign のところの if でチェックしてもよいかと思います。好みの範囲かと思います。


# Determine the sign
sign = 1
if s[index] == "+":
index += 1
elif s[index] == "-":
sign = -1
index += 1
Comment on lines +223 to +228
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if s[index] in "+-":
  if s[index] == "-":
    sign = -1
  index += 1

などでもよいですかね。


# Read the integer
value = 0
while index < len(s):
if s[index] not in string.digits:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

感覚的に s[index].isdigit() の方をよく使う気がします。実装を見てこの書き方と違いがあるのか調べてみてもいいかもしれません。
https://docs.python.org/ja/3.13/library/stdtypes.html#str.isdigit

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

一応以下のような違いを想定して書いてます。

import string

# 半角
print("1".isdigit())  # True
print("1" in string.digits)  # True
# 全角
print("1".isdigit())  # True
print("1" in string.digits)  # False
# カローシュティー数字
print("੭".isdigit())  # True

https://docs.python.org/3/library/stdtypes.html#str.isdigit
には以下のようにあり、結構広い範囲で数字判定されそうです。

Digits include decimal characters and digits that need special handling, such as the compatibility superscript digits.

https://docs.python.org/3/library/string.html#string.digits
はノーマルな数字のみ。

The string '0123456789'.

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
Comment on lines +243 to +253
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

is_overflow = result > INT_MAX // 10 or (result == INT_MAX // 10 and digit > INT_MAX % 10)

などとまとめることはできますね。意図的に分けたのかもしれませんが、個人的には特にマイナスのときが読みづらく感じました。。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ご提案としては sign == 1 の場合に記載いただいた is_overflow を返り値にできるということであっていますか?

```

7分,6分,5分で3回Accept