diff --git a/8. String to Integer (atoi).md b/8. String to Integer (atoi).md new file mode 100644 index 0000000..47cecca --- /dev/null +++ b/8. String to Integer (atoi).md @@ -0,0 +1,190 @@ +文字を1文字ずつ読み進めれば良い。フェーズとしては以下の3つ +- 空白を無視して読み進める。 +- 符号を判定する +- 数値を読んでいく + +INT_MAX, INT_MINを超えたらそこで終了する。Pythonのintは理論上は上限がないので途中のオーバーフローなどは考えなくて良い。 +CやC++で実装する場合でも32bitより大きい型(long)を確保してあげれば考えなくて良くなる。← ただlongの場合のオーバーフローの考慮などどこかではちゃんと考慮しないといけない。その場合は、桁上りの計算をする前にオーバーフローするかどうかを判定すれば良い。 + +> 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): + 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