- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
虽然我知道尾递归优化是非 Pythonic 的,但我想出了一个快速的 hack 来解决这里的一个问题,这个问题在我准备发布时就被删除了。
在 1000 个堆栈限制下,深度递归算法在 Python 中不可用。但有时通过解决方案进行初步思考非常有用。由于函数在 Python 中是一流的,所以我尝试返回一个有效函数和下一个值。然后在循环中调用该过程,直到完成单个调用。我敢肯定这不是新的。
我发现有趣的是,我预计来回传递函数的额外开销会使它比正常递归慢。在我的粗略测试中,我发现它花费了正常递归时间的 30-50%。 (还有允许 LONG 递归的额外好处。)
这是我正在运行的代码:
from contextlib import contextmanager
import time
# Timing code from StackOverflow most likely.
@contextmanager
def time_block(label):
start = time.clock()
try:
yield
finally:
end = time.clock()
print ('{} : {}'.format(label, end - start))
# Purely Recursive Function
def find_zero(num):
if num == 0:
return num
return find_zero(num - 1)
# Function that returns tuple of [method], [call value]
def find_zero_tail(num):
if num == 0:
return None, num
return find_zero_tail, num - 1
# Iterative recurser
def tail_optimize(method, val):
while method:
method, val = method(val)
return val
with time_block('Pure recursion: 998'):
find_zero(998)
with time_block('Tail Optimize Hack: 998'):
tail_optimize(find_zero_tail, 998)
with time_block('Tail Optimize Hack: 1000000'):
tail_optimize(find_zero_tail, 10000000)
# One Run Result:
# Pure recursion: 998 : 0.000372791020758
# Tail Optimize Hack: 998 : 0.000163852100569
# Tail Optimize Hack: 1000000 : 1.51006975627
为什么第二种方式更快?
我的猜测是在堆栈上创建条目的开销,但我不确定如何找出答案。
编辑:
在玩调用计数时,我做了一个循环来尝试使用不同的 num 值。当我多次循环和调用时,递归更接近奇偶校验。
因此,我在时间之前添加了这个,即新名称下的 find_zero:
def unrelated_recursion(num):
if num == 0:
return num
return unrelated_recursion(num - 1)
unrelated_recursion(998)
现在,尾部优化调用占整个递归时间的 85%。
所以我的理论是 15% 的惩罚是更大堆栈的开销,而不是单个堆栈。
我看到每次只运行一次时执行时间有如此巨大差异的原因是分配堆栈内存和结构的惩罚。一旦分配,使用它们的成本就会大大降低。
因为我的算法非常简单,所以内存结构分配占了执行时间的很大一部分。
当我减少对 unrelated_recursion(499)
的堆栈启动调用时,我在 find_zero(998)
执行时间内获得了完全启动和未启动堆栈之间的一半。这在理论上是有道理的。
最佳答案
作为评论希望提醒我,我并没有真正回答这个问题,所以这是我的观点:
在您的优化中,您正在分配、解包和解除分配元组,因此我尝试不使用它们:
# Function that returns tuple of [method], [call value]
def find_zero_tail(num):
if num == 0:
return None
return num - 1
# Iterative recurser
def tail_optimize(method, val):
while val:
val = method(val)
return val
对于 1000 次尝试,每次尝试都以 value = 998 开始:
(请注意,对我而言,您的优化版本比未优化版本更快......但我们不会进行完全相同的测试。)
但我认为这对获取这些统计数据没有用:成本更多地在 Python 方面(方法调用、元组分配等),而不是您的代码执行实际操作。在实际应用程序中,您最终不会测量 1000 个元组的成本,而是实际实现的成本。
但千万不要这样做:这几乎是白白难读,你是在为读者而不是机器写作:
# Function that returns tuple of [method], [call value]
def find_zero_tail(num):
if num == 0:
return None, num
return find_zero_tail, num - 1
# Iterative recurser
def tail_optimize(method, val):
while method:
method, val = method(val)
return val
我不会尝试实现一个更具可读性的版本,因为我最终可能会:
def find_zero(val):
return 0
但我认为在实际情况下有一些很好的方法来处理递归限制(在内存大小或深度方面):
为了帮助解决内存(不是深度)问题,functools 中的 lru_cache 通常会有很大帮助:
>>> from functools import lru_cache
>>> @lru_cache()
... def fib(x):
... return fib(x - 1) + fib(x - 2) if x > 2 else 1
...
>>> fib(100)
354224848179261915075
对于堆栈大小,您可以使用 list
或 deque
,这取决于您的上下文和用法,而不是使用语言堆栈。根据确切的实现(当您实际上将简单的子计算存储在堆栈中以重新使用它们时)它被称为 dynamic programming :
>>> def fib(x):
... stack = [1, 1]
... while len(stack) < x:
... stack.append(stack[-1] + stack[-2])
... return stack[-1]
...
>>> fib(100)
354224848179261915075
但是,使用您自己的结构而不是调用堆栈的好处来了,您并不总是需要保留整个堆栈来继续计算:
>>> def fib(x):
... stack = (1, 1)
... for _ in range(x - 2):
... stack = stack[1], stack[0] + stack[1]
... return stack[1]
...
>>> fib(100)
354224848179261915075
但总结一下“在尝试实现之前先了解问题”(不可读、难以调试、难以视觉证明,这是糟糕的代码,但很有趣):
>>> def fib(n):
... return (4 << n*(3+n)) // ((4 << 2*n) - (2 << n) - 1) & ((2 << n) - 1)
...
>>>
>>> fib(99)
354224848179261915075
如果你问我,最好的实现是更具可读性的(对于 Fibonacci 示例,可能是具有 LRU 缓存但通过更改 ... if ... else ...
使用更具可读性的 if 语句,对于另一个示例,deque
可能更具可读性,对于其他示例,动态编程可能更好......
“您是为阅读您的代码的人编写代码,而不是为机器编写代码”。
关于python - 为什么尾递归优化比 Python 中的普通递归更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37193076/
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 5 年前。
DBMS 供应商使用 SQL 方言特性来区分他们的产品,同时声称支持 SQL 标准。 'Nuff 说。 您编写的任何 SQL 示例是否无法转换为 SQL:2008 标准 SQL? 具体来说,我说的是
多年来,我一直在考虑这个问题,但从未成功实现过。我说的是一个快速、高效的 C 函数,它在输入中接受一个整数值(例如 16 位),并在输出中给出完全不同的相同位大小的数字,但“考虑到”所有数字已经给出了
当标准 iPhone UI 控件变得过于平淡,并且您希望简单的记分应用程序通过颜色、动画、非标准 GUI 字体和背景壁纸等流行时。 ,为这样的事情集成游戏引擎有意义吗? 我对 Unity3D 和 To
这是我的第一个问题,所以如果我没有正确地标记标签,我很抱歉。我尝试过...这是我的问题:我希望有人能告诉我如何为普通的表格 View 创建 2 行节标题。我遇到的问题是:1)我找不到可以模仿默认 1
所以我一直在开发一个仅使用普通 JavaScript 的“非常简单”的计算器。但我不知道为什么它现在起作用了。 这是我的 JavaScript 和 HTML 代码: (function() { "
我正在尝试编写一个函数来满足以下要求: 给定一个对象和一个键,“getElementsThatEqual10AtProperty”返回一个数组,其中包含位于给定键处等于 10 的数组的所有元素。 注释
[最终编辑:我觉得有必要做出回应,因为我从这篇文章中学到了很多东西(主要是通过你们,我花了更多的时间来理解CSS..但最后,我真的不知道如何为了使这项工作有效..除了真正破坏html的基本结构..我不
我希望能够将一个函数附加到一个元素上,该函数只有在该元素上单击指定时间后才会运行。 有几个( 1 、 2 、 3 )与在 javascript 中处理鼠标保持相关的问题;但这些问题要么使用 jQuer
我想将泛型函数保存为变量: (defvar *gf* (make-instance 'standard-generic-function) 但是在添加方法时,我必须自己定义call-next-meth
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
我有一个普通的 tableView——一个部分——当我滚动时,行出现在标题后面。像这样: 有没有简单的方法来防止这种情况?我认为它与 contentInset 有关,但这也会移动标题,这不是我想要的。
你好,我有一个ajax表单,它返回一个控制“发送”动画的脚本。然而,当淡入开始时,它会停止在 0.1 不透明度。我不确定脚本中有什么问题。任何帮助将不胜感激。 quote_form = documen
这是演示我的问题的代码笔:http://codepen.io/PiotrBerebecki/pen/yaWQwZ 目标是当用户点击时有滚动动画 顶部导航链接,以及 Back to Top 按钮在右下角
在我重新发明轮子之前,纯Java中有类似主题的并发队列吗?我有以下要求: 多个读者/消费者 多名作家/制片人 每条消息都必须由每个(活跃的)消费者消费 在每个消费者阅读一条消息后,它应该变成垃圾(即不
这个问题与 Do MySQL tables need an ID? 有一个无意义的auto_incremental ID作为一个表的PRIMARY KEY,那么我创建其他KEY时,我是否应该在KEY中
我有一个普通 UITableView 并且我想隐藏分隔符。为了隐藏它,我尝试使用以下属性: 我也在 viewDidLoad 中设置了它。 self.tableView.separatorStyle =
var vettore = document.getElementById(id_form).elements; for (var i = 0; i '+vettore_nomi_file[i]; 最
我已经构建了一个非常简单的轮播,但有一个问题。在我的轮播中,我有三张幻灯片,一个上一个按钮和一个下一个按钮。我想要的是当我单击下一个按钮并在最后一张幻灯片上转到第一张幻灯片时。此外,当我单击上一个按钮
我是 javascript 的新手,所以我需要一些帮助。 我正在尝试制作一个简单的插件(当然只是为了学习,以便更好地理解事物),但我遇到了一些麻烦,我将不胜感激。 我的插件是基本的,我正在尝试为 sc
我是一名优秀的程序员,十分优秀!