Skip to content

8. String to Integer (atoi)

On LeetCode ->

Reformulated question

Parse a string into a 32-bit signed integer.

  • Ignore leading spaces.
  • Read one optional + or -.
  • Read consecutive digits only.
  • If no digit is read, return 0.
  • Clamp result to \([-2^{31}, 2^{31} - 1]\).

Example:

  • " -042abc" -> -42
    • skip spaces
    • read -
    • read 042
    • stop at a

Key trick

Build the number left to right and check overflow before doing:

num = num * 10 + digit

This avoids converting a huge substring first and handles clamping cleanly.

Trap

  • Accepting sign after digits, like "0-1".
  • Accepting multiple signs, like "-+12".
  • Forgetting that no digits after sign means 0, like "+".
  • Using strip() can hide that only leading spaces should be ignored.
  • Overflow check differs for positive and negative bounds.

Why is this question interesting?

It is a small parser.

It tests careful state handling, edge cases, and overflow logic more than Python syntax.

Solve the problem with idiomatic python

class Solution:
    def myAtoi(self, s: str) -> int:
        INT_MIN = -(2 ** 31)
        INT_MAX = 2 ** 31 - 1

        i = 0
        n = len(s)

        # Skip leading spaces only.
        while i < n and s[i] == " ":
            i += 1

        # Read optional sign.
        sign = 1
        if i < n and s[i] in "+-":
            if s[i] == "-":
                sign = -1
            i += 1

        # Read digits and clamp on the fly.
        value = 0
        while i < n and s[i].isdigit():
            digit = ord(s[i]) - ord("0")

            if sign == 1 and value > (INT_MAX - digit) // 10:
                return INT_MAX
            if sign == -1 and value > ((-INT_MIN) - digit) // 10:
                return INT_MIN

            value = value * 10 + digit
            i += 1

        return sign * value

Pytest test

Use one parametrized test covering normal parse, stop-on-nondigit, invalid start, sign handling, and clamping.

import pytest

@pytest.mark.parametrize(
    ("s", "expected"),
    [
        ("42", 42),
        ("   -042", -42),
        ("1337c0d3", 1337),
        ("0-1", 0),
        ("words and 987", 0),
        ("+", 0),
        ("-+12", 0),
        ("   +0 123", 0),
        ("2147483647", 2147483647),
        ("2147483648", 2147483647),
        ("-2147483648", -2147483648),
        ("-2147483649", -2147483648),
        ("", 0),
    ],
)
def test_my_atoi(s, expected):
    assert Solution().myAtoi(s) == expected

Comment my solution

Good:

  • Correct overall parsing order.
  • Overflow is checked before updating the number.
  • Tests cover many important cases.

Can be improved:

  • digits = [] stores all digits first, but you can parse and clamp in one pass.
  • isspace() is broader than needed; the problem only mentions space, though it is usually still accepted.
  • print("foo") and print("bar") should not be there.
  • int(d) is done twice in the loop.
  • A few useful tests are missing, like "", "+", " +0 123", and "2147483647".
import pytest

class Solution:
    def myAtoi(self, s: str) -> int:
        i = 0
        sign = 1

        # skip whitespaces
        while i < len(s) and s[i].isspace():
            i += 1

        # sign
        if i < len(s):
            if s[i] == "-":
                sign = -1
                i +=1
            elif s[i] == "+":
                sign = 1
                i +=1

        # read up to the first non-digit
        digits = []
        while i < len(s) and s[i].isdigit():
            digits.append(s[i])
            i += 1

        n = 0

        for d in digits:
            d = int(d)
            # check if n*10 + digits.pop() is signed 32-bit
            # if not we round it.
            if sign == -1 and (n > (2**31 - d) // 10):
                print("foo")
                return - 2**31
            if sign == 1 and (n > (2**31 - 1 - d) // 10):
                print("bar")
                return 2**31 - 1
            n = n*10 + int(d)

        return sign*n


@pytest.mark.parametrize(
    ("s", "expected"),
    [
        ("42", 42),
        ("  42", 42),
        ("-042", -42),
        ("+1", 1),
        ("1337c0d3", 1337),
        ("0-1", 0),
        ("-2147483649", -2**31),      # -(2**31 + 1) # -2147483649
        ("-1099511627776", -2**31),   # -(2**40) # -2147483648
        ("2147483648", 2**31 - 1),    # 2**31 # 2147483648
        ("1099511627776", 2**31 - 1), # 2**40 # 2147483648
        # not correct input, return 0
        ("words and 987", 0),
        ("-+12", 0)
    ]
)
def test_myAtoi(s, expected):
    assert Solution().myAtoi(s) == expected

# ord() is used in AI generated solution
#
# ord("a") # 97
# ord("b") # 98
# ord("c") # 99
# ord("0") # 48
# ord("1") # 49
# ord("2") # 50
# ord("1") - ord("0") # 1
# ord("2") - ord("0") # 2