Как можно оптимизировать, улучшить код, есть ли у него более хорошая альтернатива?
Написал код, выделяющий из строки все числа списком.
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 шт):
Вариант 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 можно еще конечно немного оптимизировать ваш вариант, добавив несколько переменных, для сохранения начальной и конечной позиции текущего числа и обойтись одним проходом по тексту (одним циклом), используя много проверок, но мне было откровенно лень это делать)
Касаемо функции из вопроса, при вызове:
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.
Добавил свой вариант 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