опасность рекурсивных функций
часто люди говорят, что не рекомендуется использовать рекурсивные функции в python (ограничения глубины рекурсии, потребление памяти и т. д.)
Я взял пример перестановки из этот вопрос.
def all_perms(str):
if len(str) <=1:
yield str
else:
for perm in all_perms(str[1:]):
for i in range(len(perm)+1):
yield perm[:i] + str[0:1] + perm[i:]
после этого я преобразовал его в не рекурсивную версию (я новичок python)
def not_recursive(string):
perm = [string[0]]
for e in string[1:]:
perm_next = []
for p in perm:
perm_next.extend(p[:i] + e + p[i:] for i in range(len(p) + 1))
perm = perm_next
for p in perm:
yield p
и я сравнил их
before=time()
print len([p for p in all_perms("1234567890")])
print "time of all_perms %i " % (time()-before)
before=time()
print len([p for p in not_recursive("1234567890")])
print "time of not_recursive %i " % (time()-before)
before=time()
print len([p for p in itertools.permutations("1234567890")])
print "time of itertools.permutations %i " % (time()-before)
результаты довольно интересные. Рекурсивная функция-это быстрый 5 сек, тогда не рекурсивный 8 сек, а затем buildin 35 сек.
так рекурсивные функции это плохо в Python? Что не так со встроенными itertools.перестановки ?
4 ответов
рекурсия "плохая" в Python, потому что она обычно медленнее, чем итеративное решение, и потому, что глубина стека Python не неограничена (и нет оптимизации хвостового вызова). Для функции sum да, вы, вероятно, хотите неограниченную глубину, так как было бы вполне разумно суммировать список из миллиона чисел, и Дельта производительности станет проблемой с таким большим количеством элементов. В этом случае вы не должны использовать рекурсию.
Если вы идете DOM дерево, считанное из XML-файла, с другой стороны, вы вряд ли превысите глубину рекурсии Python (по умолчанию 1000). Это конечно может, но с практической точки зрения это, вероятно, не будет. Если вы заранее знаете, с какими данными будете работать, вы можете быть уверены, что не переполните стек.
рекурсивная прогулка по дереву, на мой взгляд, гораздо более естественна для записи и чтения, чем итеративная, и рекурсия обычно является небольшой частью время выполнения. Если это действительно важно для вас, что это занимает 16 секунд вместо 14, бросая PyPy на это может быть лучшее использование вашего времени.
рекурсия кажется естественной для проблемы, которую вы опубликовали, и если вы считаете, что код легче читать и поддерживать таким образом, и производительность адекватна, тогда идите на это.
Я вырос, написав код на компьютерах, которые, как практический вопрос, ограничили глубину рекурсии примерно до 16, если она была предоставлена вообще, поэтому 1000, кажется, роскошно для меня. :-)
рекурсия хороший для проблем, которые поддаются чистым, ясным, рекурсивным реализациям. Но, как и все Программирование, вы должны выполнить некоторый анализ алгоритмов, чтобы понять характеристики производительности. В случае рекурсии, помимо количества операций, необходимо также оценить максимальную глубину стека.
большинство проблем возникают, когда новые программисты предполагают, что рекурсия волшебна и не понимают, что под ней есть стек, делающий это возможным. Новый программисты также известны тем, что выделяют память и никогда не освобождают ее, и другими ошибками, поэтому рекурсия не уникальна в этой опасности.
ваши тайминги полностью неверны:
def perms1(str):
if len(str) <=1:
yield str
else:
for perm in perms1(str[1:]):
for i in range(len(perm)+1):
yield perm[:i] + str[0:1] + perm[i:]
def perms2(string):
perm = [string[0]]
for e in string[1:]:
perm_next = []
for p in perm:
perm_next.extend(p[:i] + e + p[i:] for i in range(len(p) + 1))
perm = perm_next
for p in perm:
yield p
s = "01235678"
import itertools
perms3 = itertools.permutations
теперь проверьте его с помощью timeit:
thc:~$ for i in 1 2 3; do echo "perms$i:"; python -m timeit -s "import permtest as p" "list(p.perms$i(p.s))"; done
perms1:
10 loops, best of 3: 23.9 msec per loop
perms2:
10 loops, best of 3: 39.1 msec per loop
perms3:
100 loops, best of 3: 5.64 msec per loop
Как видите,itertools.permutations
является самым быстрым, за ним следует рекурсивная версия.
но обе функции pure Python не имели шансов быть быстрыми, потому что они выполняют дорогостоящие операции, такие как добавление списков ala perm[:i] + str[0:1] + perm[i:]
Я не могу воспроизвести ваши результаты синхронизации (в Python 2.6.1 на Mac OS X):
>>> import itertools, timeit
>>> timeit.timeit('list(all_perms("0123456789"))',
... setup='from __main__ import all_perms'),
... number=1)
2.603626012802124
>>> timeit.timeit('list(itertools.permutations("0123456789"))', number=1)
1.6111600399017334