Skip to content

Create 0008-string-to-integer-atoi.md - #60

Open
naoto-iwase wants to merge 2 commits into
mainfrom
0008-string-to-integer-atoi
Open

Create 0008-string-to-integer-atoi.md#60
naoto-iwase wants to merge 2 commits into
mainfrom
0008-string-to-integer-atoi

Conversation

@naoto-iwase

Copy link
Copy Markdown
Owner

class Solution:
def myAtoi(self, s: str) -> int:
whole_match = re.match("\s*([\+\-]?)([0-9]+)", s)
if whole_match:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

マッチしない場合は None が返ってくるようなので、ここは if not whole_match: か if whole_matchi is None: のときに return 0 して if 文の中の2行を外に出した方が個人的な好みかなと思いました。ただそうすると空行を入れる場所が難しいなと思いました。

https://docs.python.org/3/library/re.html#re.match

Return None if the string does not match the pattern; note that this is different from a zero-length match.

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.

ありがとうございます。

同意です。
先にearly returnするnot matchから消しておけば読み手は残りの状況だけを考えれば良いので、可読性が高いと感じます。

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.

空行の位置は、下の実装2、実装4のように、if not match: return 0の直後にだけ入れるイメージです。(気持ち的に)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

確かに、実装2と4の if not match の直後の空行に個人的に違和感は感じなかったのでそこがいいかもしれません。参考になります。

- https://discord.com/channels/1084280443945353267/1237649827240742942/1354495583494082580
- > 漢字などでも True になるのは Python の isdigit の話ですね。
- `str.isdecimal()`, `str.isdigit()`, `str.isnumeric()`があるらしい。
- `isdecimal()`: 最も厳密。Unicode の一般カテゴリ"Nd"(Number, Decimal unit)、つまり全角も含めたアラビア数字(十進数字という)だけを許す。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分はこちらの関数を調べられておらず参考になりました。

sign = 1
i += 1
else:
sign = 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"+" や "-" がない場合についてのコメントを残して、以下のようにするのも選択肢としてよさそうかなと思いました。

	    # Determine the sign. Assume '+' if it is not specified.
	    sign = 1
        if s[i] == "-":
            sign = -1
            i += 1
        elif s[i] == "+":
            i += 1

return 0

signed_integer = int(sign + digits)
if signed_integer < -2**31:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

signed_integer = max(signed_integer, -2**31)
signed_integer = min(signed_integer, 2**31 - 1)

のほうがシンプルに感じました。


class Solution:
def myAtoi(self, s: str) -> int:
whole_match = re.match("\s*([\+\-]?)([0-9]+)", s)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

正規表現は読み手に取って認知負荷が高くなる場合があると思います。どのような文字列にマッチするパターンなのか、コメントで説明するとよいかもしれません。

sign = 1

if sign == -1:
abs_limit = -self.INT_MIN

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

念の為ですが、この値は32bitをこえていますよね。Python なのでそんなに気にすることではないかもしれませんが。

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.

ありがとうございます。

気づいてなかったです。お察しの通り、ここは他言語互換で32bit整数のつもりで書いていたので設計上のミスです。

abs_limitを使うのはやめて、単に、

        # check overflow
        if sign == -1:
            if value < (self.INT_MIN + digit) // 10:
                return self.INT_MIN
        else:
            if value > (self.INT_MAX - digit) // 10:
                return self.INT_MAX

とする方が良さそうですね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

前提として value は符号付きの値ですよね。
負の数の割り算における切り捨ての影響で正しく動かないと思います。digit にオーバーフローすべき大きい値が来た時、self.INT_MIN + digit の十の位は 1 小さくなりますが、//10 によりその影響が消されてしまいます。

ご参考として私は以下のように上の桁と追加する digit をそれぞれ比較するように書きました。
https://github.com/tokuhirat/LeetCode/pull/59/files#diff-ed1cf3d46cfd4bddc1808bc5157b0cc7596de0a5858c3d1da82c0d1b1006874cR242

    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

@naoto-iwase naoto-iwase Nov 15, 2025

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.

ありがとうございます。

おっしゃるとおりでした... 鋭いご指摘をありがとうございます。
10 * valueとdigitを別々に扱う処理も大変参考になります。

一方で、元のoverflowの条件式自体は単一の不等式になるはずなので、丸めを同値変形を維持して行うことで、単一式での判定を維持するべきだと考えます。

sign == -1のとき、10 * value - digit_abs < self.INT_MIN、つまりvalue < (self.INT_MIN + digit_abs) / 10がoverflow条件です。ここで天井関数、つまりmath.ceil()とするのが簡単ですが、これは商を一時的に浮動小数点数で表すことになるので避けます。これを// のような切り捨て除算、つまり床関数で表現するためには、非除数を9足しておけばいいと考えました。

たとえば、まず[-100, -99, -98, ..., -91, -90]を/10してmath.ceil()とすると[-10, -9, -9, ..., -9, -9]となります。
これを、被除数を先に+9して//10するとしたら、[-91, -90, -89, ..., 82, -82] -> [-10, -9, -9, ..., -9, -9]と同じ結果を得られます。

# read digits
value = 0
while i < n and s[i].isdecimal():
    digit_abs = int(s[i])
    # check overflow
    if sign == -1:
        # value < math.ceil((self.INT_MIN + digit_abs) / 10)
        if value < (self.INT_MIN + digit_abs + 10 - 1) // 10:
            return self.INT_MIN
    else:
        # value > math.floor((self.INT_MAX - digit_abs) / 10)
        if value > (self.INT_MAX - digit_abs) // 10:
            return self.INT_MAX
    value = value * 10 + sign * digit_abs
    i += 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私が書いたコードは冗長に感じていましたので、お書きいただいたコードはシンプルで良いなと思いました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants