LeetCode 65: Valid Number (Python) Problem

Validate if a given string can be interpreted as a decimal or scientific number.

Some examples:

"0" =&gt; true
" 0.1 " =&gt; true
"abc" =&gt; false
"1 a" =&gt; false
"2e10" =&gt; true
" -90e3   " =&gt; true
" 1e" =&gt; false
"e3" =&gt; false
" 6e-1" =&gt; true
" 99e2.5 " =&gt; false
"53.5e93" =&gt; true
" --6 " =&gt; false
"-+3" =&gt; false
"95a54e53" =&gt; false


Code

I've solved the valid number LeetCode problem using Python re module. If you'd like to review the code and provide any change/improvement recommendations, please do so and I'd really appreciate that.

import re
from typing import Optional    

def is_numeric(input_string: Optional[str]) -&gt; bool:
    """
    Returns True for valid numbers and input string can be string or None
    """
    if input_string is None:
        return False

    expression_d_construct = r"^[+-]?(?:\d*\.\d+|\d+\.\d*|\d+)[Ee][+-]?\d+$|^[+-]?(?:\d*\.\d+|\d+\.\d*|\d+)$|^[+-]?\d+$"
    expression_char_class = r"^[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*|[0-9]+)[Ee][+-]?[0-9]+$|^[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*|[0-9]+)$|^[+-]?[0-9]+$"

    if re.match(expression_d_construct, input_string.strip()) is not None and re.match(expression_char_class, input_string.strip()) is not None:
        return True
    return False


if __name__ == "__main__":
        # ---------------------------- TEST ---------------------------
    DIVIDER_DASH = '-' * 50
    GREEN_APPLE = '\U0001F34F'
    RED_APPLE = '\U0001F34E'

    test_input_strings = [None, "0  ", "0.1", "abc", "1 a", "2e10", "-90e3",
                          "1e", "e3", "6e-1", "99e2.5", "53.5e93", "--6", "-+3", "95a54e53"]

    count = 0
    for string in test_input_strings:
        print(DIVIDER_DASH)
        if is_numeric(string):
            print(f'{GREEN_APPLE} Test {int(count + 1)}: {string} is a valid number.')
        else:
            print(f'{RED_APPLE} Test {int(count + 1)}: {string} is an invalid number.')
        count += 1


Output

--------------------------------------------------
 Test 1: None is an invalid number.
--------------------------------------------------
 Test 2: 0   is a valid number.
--------------------------------------------------
 Test 3: 0.1 is a valid number.
--------------------------------------------------
 Test 4: abc is an invalid number.
--------------------------------------------------
 Test 5: 1 a is an invalid number.
--------------------------------------------------
 Test 6: 2e10 is a valid number.
--------------------------------------------------
 Test 7: -90e3 is a valid number.
--------------------------------------------------
 Test 8: 1e is an invalid number.
--------------------------------------------------
 Test 9: e3 is an invalid number.
--------------------------------------------------
 Test 10: 6e-1 is a valid number.
--------------------------------------------------
 Test 11: 99e2.5 is an invalid number.
--------------------------------------------------
 Test 12: 53.5e93 is a valid number.
--------------------------------------------------
 Test 13: --6 is an invalid number.
--------------------------------------------------
 Test 14: -+3 is an invalid number.
--------------------------------------------------
 Test 15: 95a54e53 is an invalid number.


RegEx Circuit

jex.im visualizes regular expressions: 





RegEx Demo 1

RegEx Demo 2

If you wish to explore the expression, it's been explained on the top right panel of regex101.com. If you'd like, you can also watch in this link, how it would match against some sample inputs.



Source

LeetCode Valid Number
Instead of diving into cumbersome and lengthy regex expressions consider the following improvement/correction:

The main thesis for the underlying aspect is:


  Numeric literals containing a decimal point or an exponent sign
  yield floating point numbers.


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

Therefore Python treats values like 53.5e93, -90e3 as float type numbers.

Eventually I would proceed with the following approach (retaining those cute icons) including additional small optimizations:

from typing import TypeVar, Optional


def is_numeric(input_string: Optional[str]) -&gt; bool:
    """
    Returns True for valid numbers. Acceptable types of items: str or None
    """
    if input_string is None:
        return False

    try:
        input_string = input_string.strip()
        float(input_string)
    except ValueError:
        return False
    return True


if __name__ == "__main__":
    # ---------------------------- TEST ---------------------------
    DIVIDER_DASH = '-' * 50
    GREEN_APPLE = '\U0001F34F'
    RED_APPLE = '\U0001F34E'

    test_input_strings = [None, "0  ", "0.1", "abc", "1 a", "2e10", "-90e3",
                          "1e", "e3", "6e-1", "99e2.5", "53.5e93", "--6", "-+3", "95a54e53"]

    count = 0
    for string in test_input_strings:
        print(DIVIDER_DASH)
        count += 1

        if is_numeric(string):
            print(f'{GREEN_APPLE} Test {count}: `{string}` is a valid number.')
        else:
            print(f'{RED_APPLE} Test {count}: `{string}` is not a valid number.')


The output:

--------------------------------------------------
 Test 1: `None` is not a valid number.
--------------------------------------------------
 Test 2: `0  ` is a valid number.
--------------------------------------------------
 Test 3: `0.1` is a valid number.
--------------------------------------------------
 Test 4: `abc` is not a valid number.
--------------------------------------------------
 Test 5: `1 a` is not a valid number.
--------------------------------------------------
 Test 6: `2e10` is a valid number.
--------------------------------------------------
 Test 7: `-90e3` is a valid number.
--------------------------------------------------
 Test 8: `1e` is not a valid number.
--------------------------------------------------
 Test 9: `e3` is not a valid number.
--------------------------------------------------
 Test 10: `6e-1` is a valid number.
--------------------------------------------------
 Test 11: `99e2.5` is not a valid number.
--------------------------------------------------
 Test 12: `53.5e93` is a valid number.
--------------------------------------------------
 Test 13: `--6` is not a valid number.
--------------------------------------------------
 Test 14: `-+3` is not a valid number.
--------------------------------------------------
 Test 15: `95a54e53` is not a valid number.