Valid Number


In [1]:
import sys; sys.path.append('../..')
from puzzles import leet_puzzle
leet_puzzle('valid-number')


Source : https://leetcode.com/problems/valid-number

Validate if a given string is numeric.

Some examples:
"0" => true
" 0.1 " => true
"abc" => false
"1 a" => false
"2e10" => true

Note: It is intended for the problem statement to be ambiguous. You should gather all requirements up front before implementing one.

Update (2015-02-10):
The signature of the C++ function had been updated. If you still see your function signature accepts a const char * argument, please click the reload button to reset your code definition.


In [2]:
def is_number(input_string):
    """
    Determine if the input_string is a valid number or not, for example:
    
    >>> is_number("0.1")
    True
    >>> is_number("abc")
    False

    - Numbers in scientific notation are allowed.
    - Only '.' as decimal point is accepted.
    - No thousands separators are allowed.
    
    :param input_string: The input string to check.
    
    :return: True if input_string is a valid number, otherwise False.
    """
    input_string = input_string.strip(' ')
    
    if input_string in ('.', ''):
        return False
    
    input_string = input_string.lstrip('+-')
    
    last_is_digit = False
    had_decimal_point = False
    had_e = False
    last = len(input_string) - 1
    
    for i, c in enumerate(input_string):
    
        # process allowed non digit characters
    
        if c == 'e':
            # 'e' is only valid if it is not the last character
            # there is only one occurence, and it must follow a digit
            if i == last or had_e or not last_is_digit:
                return False
            had_e = True
        
        elif c == '.':
            # decimal point is only valid if we didn't already have
            # a scientific notation character, and it is the only
            # decimal point
            if had_decimal_point or had_e:
                return False
            had_decimal_point = True
        
        elif c in ('+', '-'):
            # + / - is only valid when not the first character if
            # it occurs as part of scientific notation.
            if last_c != 'e':
                return False 
        
        elif not c.isdigit():
            return False
        
        else:
            last_is_digit = True
            
        last_c = c
            
    return True


def is_number_using_float(input_string):
    try:
        float(input_string)
    except ValueError:
        return False
    return True

            
test_cases = [
    "", "0", "0.1", " 0.1", "abc", "4e10", "4.5e10", "4.5e10e10",
    "-4", "+4", " ", "...", ".0.0.0.", "1.abc", "e", "10e", "1e2",
    ".31", " . ", ".", "x.y.z", "2.4.6", ".1", "1.", "..1", "1..",
    "6e6.6", "0042032e+6", "0042032e6+6", "+.1", "-1.", "..1", "1..", 
    "1+", "1-", "1 2 3 4 ", "1.2 3", ".1.2.3.", "_123", " e ", ".e",
    "1,345,344.00"
]


for test_case in test_cases:
    actual = is_number(test_case)
    expected = is_number_using_float(test_case)
    if actual != expected:
        assert False, ('is_number("' + test_case + '") == ' + 
                       str(actual) + ' != ' + str(expected))
        
print str(len(test_cases)) + ' tests passed'


42 tests passed

In [3]:
%%timeit
is_number("0.34834")


The slowest run took 4.55 times longer than the fastest. This could mean that an intermediate result is being cached 
100000 loops, best of 3: 3.51 µs per loop

In [4]:
%%timeit
is_number_using_float("0.34834")


The slowest run took 12.09 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 335 ns per loop

The timings here demonstrate that it really isn't worth implementing your own float to string function. Best just used the build in conversion which probably uses optimised c code.