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
190 changes: 190 additions & 0 deletions 8. String to Integer (atoi).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
文字を1文字ずつ読み進めれば良い。フェーズとしては以下の3つ
- 空白を無視して読み進める。
- 符号を判定する
- 数値を読んでいく

INT_MAX, INT_MINを超えたらそこで終了する。Pythonのintは理論上は上限がないので途中のオーバーフローなどは考えなくて良い。
CやC++で実装する場合でも32bitより大きい型(long)を確保してあげれば考えなくて良くなる。← ただlongの場合のオーバーフローの考慮などどこかではちゃんと考慮しないといけない。その場合は、桁上りの計算をする前にオーバーフローするかどうかを判定すれば良い。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

long はデータモデルによってサイズが異なります。
https://ja.wikipedia.org/wiki/64%E3%83%93%E3%83%83%E3%83%88
LP64 LLP64 等でお調べください。

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.

ありがとうございます。C++の規格では各typeの最小のbit数を規程していて、実際のサイズはその制約を満たす上で選択されたデータモデルによって決まるんですね。

C++の規格ではlongの最小サイズは32bitとして決められていて、LP32のようなデータモデルだと32bitになる。long long型については64bit以上であることが仕様上保証されている。


> Integers have unlimited precision.

https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex

1st

isdigitの実装は https://github.com/python/cpython/blob/2305ca51448552542b2414186252123a8dc87db7/Objects/bytes_methods.c#L154
空白の判定には、`str.isspace()`も使用できる。LeetCodeの要件的には`' '`との比較だけで良い。

```python
class Solution:
def myAtoi(self, s: str) -> int:
def skip_white_spaces():
index = 0
while index < len(s) and s[index] == ' ':
index += 1
return index

def get_sign(begin):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

文字を解析してインデックスと符号を得ているため、 get より parse という単語を使用したほうがしっくりくると思います。

index = begin
if index == len(s):
return index, 1
if s[index] == '-':
return index + 1, -1
if s[index] == '+':
return index + 1, 1
return index, 1

index = skip_white_spaces()
index, sign = get_sign(index)

INT_MAX = (1 << 31) - 1
INT_MIN = - INT_MAX - 1

number = 0
while index < len(s) and s[index].isdigit():
digit = ord(s[index]) - ord('0')
number = number * 10 + sign * digit
if number >= INT_MAX:
return INT_MAX
if number <= INT_MIN:
return INT_MIN
index += 1
return number
```

> 32-bits環境だと(1 << 31)をした時点でオーバーフローする

2nd

桁上りの掛け算をする前にオーバーフローの判定をする版。あと関数に切り出さずに書いた。処理が素直に上から下に流れるので関数に切り出さなくても大丈夫かも。
むしろposを取り回している分切り出すと逆に分かりづらいかもしれない。

```python
class Solution:
def myAtoi(self, s: str) -> int:
pos = 0
# 空白をskipする
while pos < len(s) and s[pos].isspace():
pos += 1
if pos == len(s):
return 0
# 符号の判定
sign = 1
if s[pos] == '+' or s[pos] == '-':
if s[pos] == '-':
sign = -1
pos += 1
INT_MAX = (1 << 31) - 1
INT_MIN = - (1 << 31)

cutoff = INT_MAX // 10
cutlim = INT_MAX % 10
if sign == -1:
cutlim = -INT_MIN % 10

num = 0
while pos < len(s) and s[pos].isdigit():
digit = ord(s[pos]) - ord('0')
if num > cutoff or (num == cutoff and digit > cutlim):
if sign == 1:
return INT_MAX
else:
return INT_MIN
num = num * 10 + digit
pos += 1

return num * sign
```

> 最後にsignを掛けているのでINT_MINのときに途中でオーバーフローしている気がする。たぶんそう。`digit >= cutlim`に変えてあげれば問題ないが、通常の範囲でのINT_MINなのかオーバーフローしてのINT_MINなのかを判別することができなくなる


3rd
```python
class Solution:
def myAtoi(self, s: str) -> int:
pos = 0
# ignore white spaces
while pos < len(s) and s[pos].isspace():
pos += 1
if pos == len(s):
return 0

# get sign
sign = 1
if s[pos] == '+' or s[pos] == '-':
if s[pos] == '-':
sign = -1
pos += 1
if pos == len(s):
return 0

# convert to number
INT_MAX = (1 << 31) - 1
INT_MIN = - (1 << 31)

num = 0
while pos < len(s) and s[pos].isdigit():
digit = ord(s[pos]) - ord('0')
num = num * 10 + sign * digit
if num > INT_MAX:
return INT_MAX
if num < INT_MIN:
return INT_MIN
pos += 1
return num
```

> `ord(s[pos]) - ord('0')`の代わりに`int(s[pos])`とかも選択肢としてあり。とはいえこれをするんだったら`int(s[index:])`みたいにやっていいじゃんという気持ちにもなるので、この問題的にはint使わない方が空気が読めてそう。

4th

floor divisionとmoduloの挙動: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations

```python
class Solution:
def myAtoi(self, s: str) -> int:
index = 0

# skip white spaces
while index < len(s) and s[index] == ' ':
index += 1
if index == len(s):
return 0

# determine sign
sign = 1
if s[index] == '+' or s[index] == '-':
if s[index] == '-':
sign = -1
index += 1
if index == len(s):
return 0

# convert to integer
INT_MAX = (1 << 31) - 1
INT_MIN = -INT_MAX - 1
num = 0
cutoff = INT_MAX // 10
cutlim = INT_MAX % 10
if sign == -1:
# 2の補数表現によりマイナスの場合は1を足せば良い&2のべき乗-1が9になることはない
# マイナスの場合もmoduloを取っても良いが、結果がsecond operandの符合と一致する言語仕様で処理が冗長になる
cutlim += 1
while index < len(s) and s[index].isdigit():
digit = ord(s[index]) - ord('0')
if abs(num) > cutoff or (abs(num) == cutoff and digit > cutlim):
return INT_MAX if sign == 1 else INT_MIN
num = num * 10 + digit * sign
index += 1
return num
```

ifのところはindex += 1が冗長になるがフラットに以下でも良いかも。

> sign = 1
if s[index] == '+':
index += 1
elif s[index] == '-':
index += 1
sign = -1