Как можно оптимизировать, улучшить код, есть ли у него более хорошая альтернатива?

Написал код, выделяющий из строки все числа списком.

def numbers_in_string(n, ogr=0, l=[]):
    for i in range(len(n)):
        k = 1
        if n[i].isdigit() and i >= ogr:
            while i + k < len(n) and n[i + k].isdigit():
                k += 1
                ogr = i + k
            l.append(int(n[i:i + k]))
    return l

Вроде работает, быстро и без ошибок. Что можно улучшить?


Ответы (3 шт):

Автор решения: MaxU

Вариант 1 - заменяем все "нецифры" на пробелы и в конце используем метод str.split():

res = "".join(c if c.isdigit() else " " for c in text).split()

Вариант 2 - простой RegEx:

import re

res = re.findall("(\d+)", text)

PS можно еще конечно немного оптимизировать ваш вариант, добавив несколько переменных, для сохранения начальной и конечной позиции текущего числа и обойтись одним проходом по тексту (одним циклом), используя много проверок, но мне было откровенно лень это делать)

→ Ссылка
Автор решения: OPTIMUS PRIME

Касаемо функции из вопроса, при вызове:

print( numbers_in_string('1234') )
print( numbers_in_string('5678') )

Она выдаст [1234], затем [1234, 5678] потому что значение по умолчанию - список, указан прямо в аргументах функции l = [].

Это известная особенность python, значения аргументов по умолчанию привязываются к функции один раз, в момент её создания. А если это изменяемые объекты (списки, словари), их содержимое сохраняется между вызовами. Можно пофиксить так:

def numbers_in_string(n, ogr = 0, l = None):
    if l == None: l = []

Если аргумент не передан - при вызове функции будет создан новый список.


Можно еще использовать протокол итераций :)

def numbers_in_string(string, start = 0, num_list = None):
    if num_list == None: num_list = []

    iter_ = iter(string[start:] + '_')

    for char in iter_:
        if char.isdigit(): num_list.append(0)

        while char.isdigit():
            num_list[-1] = 10 * num_list[-1] + int(char) # [-1] — последний элемент
            char = next(iter_) # внутри for на каждой итерации выполняется то же самое

    return num_list

print( numbers_in_string('1x2x3x4') ) # [1, 2, 3, 4]
print( numbers_in_string('5678') )    # [5678]

В общем случае, по завершению элементов, вызов next() выдает ошибку. for перехватывает ошибку и завершает цикл. А в строку добавлен '_' символ, чтобы гарантировать, что последним символом будет не число, и обработка ошибки последнего next() останется на for.

→ Ссылка
Автор решения: vadim vaduxa

Добавил свой вариант EXP_1 для разнообразия:

EXP_1 = '''
def qwe(Str):
    s=''
    for a in Str:
        if a.isdigit():
            s += a
        elif s:
            yield s
            s = ''
    if s:
        yield s

list(qwe(Str))
'''

EXP_2 = '''
"".join(c if c.isdigit() else " " for c in Str).split()
'''

EXP_3 = '''
re.findall("(\d+)", Str)
'''

EXP_4 = '''
def numbers_in_string(string, start = 0, num_list = None):
    if num_list == None: num_list = []

    iter_ = iter(string[start:] + '_')

    for char in iter_:
        if char.isdigit(): num_list.append(0)

        while char.isdigit():
            num_list[-1] = 10 * num_list[-1] + int(char) # [-1] — последний элемент
            char = next(iter_) # внутри for на каждой итерации выполняется то же самое

    return num_list
    
numbers_in_string(Str)
'''

Сравнение быстродействия:

if __name__ == '__main__':
    # сформировать строку для замера
    import random, string
    StrLength = 1000  # длина строки
    setup = '''import re\nStr = "%s"''' % "".join(random.choice(string.ascii_letters+string.digits) for _ in range(StrLength))
    print(setup)

    # замер времени выполнения
    import timeit
    for func in [f for f in globals() if f.startswith('EXP_')]:
        t = timeit.timeit(stmt=globals()[func], setup=setup)
        print(func, ':', t)

Вывод - вариант EXP_3 с re.findall самый быстрый:

# StrLength = 1
Str = "3"
EXP_1 : 0.6491643
EXP_2 : 0.6566974999999999
EXP_3 : 0.8050681
EXP_4 : 0.7200221

# StrLength = 10
Str = "7ux035X4It"
EXP_1 : 1.9279111999999998
EXP_2 : 2.0201605000000002
EXP_3 : 1.6469179999999999
EXP_4 : 3.4127857000000006

# StrLength = 100
Str = "cYtARwJniY4H1eCNs6MrnbgKOyElKzYfzkHyqNUO9AhhP90HeIA9qIFWXOPXtV5LNOlEvXglaLIMseBMrtdaTskvddvuoigh8JQQ"
EXP_1 : 9.5170252
EXP_2 : 13.8997921
EXP_3 : 5.136633
EXP_4 : 16.6518603

# StrLength = 1000
Str = "ncfEbi5bCB5tT0tkwJppDX7d28vkNYR2d4sUfgeIxprqDLiDFV5TRnU6UUDESYmZz0xka7iI3vyJ6OJOlFHIrhyS6rOJfdLQKZQVmaHEVyV6uGjcO2bfxm0qbL1e2CLA9EEiu59TvIPqBM1r0NAAuAdMJFDbYyUFu21QA6LL9WF70urpUsuJPOUAif1P6mbu0fTagAjePICJ1SrR4MnykpY8fWBExQDOkVuvCd5fK2k70pTMm7Tie2vSRAXuDboVqedfpbRoOHXjdv5kS2J5nQXy21gsAGaAeQte4imvOCdnrkUi1GChUU3F35EOM4Air9zZbViqEF7iM5ZhtWMcI3utEVUr9HAvQvijtkpLyrWC7LVzXUbm9DfrlG1TDOHXSi1Jp66UHH9nel0JQuKcg3wrohGG9ZgWRTRKjY8W14txwH1BfVl89hPEpLELt2Kxid0k5UOjOa4x54G1eJmhMNkGAoG7h0hVqDyFU3Bj1Wwevb1nqMfQyw7RbT1imkitG1butSHILzk4HQ5O6jZqXgogUkCHabQ3Ln40DlF0qXgLiQoz3W5OdwDYVmLb5M3czp5rTEkWkVlntXZ71OmQzU4Zh6GatEpiOKYNpTTtmDT8nIYOiw7qTS9MFSHt6kHVAL7VBam0GHfsmUrAAYWpYgFjcYWIzgW8Tc8SVbt6LQ30ryr7bzSbhWe5ls4IszoL69xdj9bsN64svh3MaUZBFnUylsYiuCFjP8LxHzwchV2n8BSersHl1Pok2cDy6eQpfhwtoGwF0pCoIcwlfguX98phr4OxYGqryJmPij6S8p8AhfyNwrvodMktsbZHXT1bJsvAKB1GwJyMn0phJyLlXqN2lksSAqHJIyImND0tc5DGbPuaFFfwZm4D728os1TxXDJA1jkdmBq3uvUJyPeTnpZCCi2uzEGq73OeiSbNJofRXvdoRP3BsVImrYUc8I9O1ucR9JU28BVsSyykXn61efMgUoWha0ekgkBECm2y"
EXP_1 : 102.257261
EXP_2 : 132.8386185
EXP_3 : 53.362674700000014
EXP_4 : 187.39318020000002
→ Ссылка