ChatGPT解决这个技术问题 Extra ChatGPT

纯函数式编程的效率

有谁知道在纯函数式编程而不是命令式编程(即允许副作用)时可能发生的最坏的渐近减速是什么?

itowlson 的评论澄清:是否有任何问题,最着名的非破坏性算法在渐近上比最着名的破坏性算法更差,如果是这样,差多少?

与命令式编程时相同,无论是什么。
@jldupont:当然要返回计算结果。存在许多无副作用的程序。除了计算输入之外,他们无能为力。但这仍然很有用。
通过糟糕地编写我的功能代码,我可以让它变得像你喜欢的那样糟糕! *咧嘴*我认为您要问的是“是否存在任何问题,其中最著名的非破坏性算法在渐近上比最知名的破坏性算法更差,如果是这样,会差多少?”......对吗? ?
你能举一个你感兴趣的减速类型的例子吗?你的问题有点含糊。
一位用户删除了他的答案,但他声称 8-queens 问题的功能版本在 n = 13 的情况下运行了超过一分钟。他承认它不是“写得很好”,所以我决定编写自己的版本F# 中的 8 个皇后:pastebin.com/ffa8d4c4。不用说,我的纯函数程序在一秒钟内计算出 n = 20。

B
Brian Campbell

根据 Pippenger [1996],当比较纯函数式(并且具有严格的评估语义,而不是惰性)的 Lisp 系统与可以改变数据的系统时,为运行在 O(n) 可以转换为纯 Lisp 中的算法,该算法在 O(n log n) 时间内运行(基于 Ben-Amram and Galil [1992] 关于模拟随机存取存储器的工作仅使用指针)。 Pippenger 还确定有一些算法是你能做的最好的。不纯系统中存在 O(n) 问题,纯系统中存在 Ω(n log n) 问题。

关于这篇论文有一些注意事项。最重要的是它不处理惰性函数式语言,例如 Haskell。 Bird, Jones and De Moor [1997] 证明 Pippenger 构建的问题可以在 O(n) 时间内用惰性函数式语言解决,但他们没有确定(据我所知,没有人确定)是否或者不是一种惰性函数式语言可以在与具有突变的语言相同的渐近运行时间内解决所有问题。

Pippenger 构造的问题要求 Ω(n log n) 是专门构造来实现这个结果的,并不一定代表实际的现实问题。这个问题有一些限制,有点出乎意料,但对于证明工作是必要的;特别是,该问题要求在线计算结果,而不能访问未来的输入,并且输入由来自无限可能原子集合的原子序列组成,而不是固定大小的集合。并且本文只为线性运行时间的不纯算法建立(下界)结果;对于需要更长运行时间的问题,有可能在线性问题中看到的额外 O(log n) 因素可能能够在算法所需的额外操作过程中被“吸收”运行时间更长。 Ben-Amram [1996] 简要探讨了这些说明和未解决的问题。

在实践中,许多算法可以用纯函数式语言实现,其效率与使用具有可变数据结构的语言相同。有关用于有效实现纯函数式数据结构的技术的良好参考,请参阅 Chris Okasaki's "Purely Functional Data Structures" [Okasaki 1998](这是他的论文 [Okasaki 1996] 的扩展版本)。

任何需要在纯函数数据结构上实现算法的人都应该阅读 Okasaki。通过使用平衡二叉树模拟可变内存,您总是可以在最坏的情况下每次操作减慢 O(log n),但在许多情况下,您可以做得比这要好得多,Okasaki 描述了许多有用的技术,从摊销技术到实际逐步完成摊销工作的时间。纯函数式数据结构可能有点难以使用和分析,但它们提供了许多好处,例如有助于编译器优化、并行和分布式计算以及版本控制、撤消和回滚等功能的实现的引用透明性。

另请注意,所有这些都只讨论渐近运行时间。许多用于实现纯函数式数据结构的技术会给您带来一定程度的常数因子减速,因为它们需要额外的簿记才能工作,以及相关语言的实现细节。纯函数式数据结构的好处可能超过这些恒定因素的减速,因此您通常需要根据所讨论的问题进行权衡。

参考

Ben-Amram, Amir 和 Galil, Zvi 1992。“关于指针与地址”ACM 杂志,39(3),第 617-648 页,1992 年 7 月

Ben-Amram, Amir 1996。“关于 Pippenger 比较纯和不纯 Lisp 的笔记”未发表的手稿,DIKU,丹麦哥本哈根大学

Bird、Richard、Jones、Geraint 和 De Moor,Oege 1997。“更匆忙,更少速度:懒惰与急切的评估”函数式编程杂志 7,5 第 541-547 页,1997 年 9 月

Okasaki, Chris 1996 年。“纯功能数据结构”博士论文,卡内基梅隆大学

Okasaki, Chris 1998。“纯功能数据结构”剑桥大学出版社,剑桥,英国

Pippenger, Nicholas 1996。“Pure Versus Impure Lisp”ACM 编程语言原则研讨会,第 104-109 页,1996 年 1 月


Pippinger 是这个问题上无可争议的权威。但我们应该强调,他的结果是理论上的,而不是实际的。在使函数式数据结构实用和高效方面,您不能比 Okasaki 做得更好。
itowlson:我必须承认我没有读足够多的皮蓬格来回答你的问题。它发表在由 Okasaki 引用的同行评审期刊上,我读了足够多的内容以确定他的说法与这个问题相关,但不足以理解证明。对于现实世界的后果,我立即得出的结论是,通过使用平衡二叉树简单地模拟可修改内存,将 O(n) 不纯算法转换为 O(n log n) 纯算法是微不足道的。有些问题不能做得比这更好;我不知道他们是否纯粹是理论上的。
Pippenger 结果做了两个限制其范围的重要假设:它考虑“在线”或“反应式”计算(不是将有限输入映射到单个输出的通常计算模型)和“符号”计算,其中输入是序列原子,只能测试是否相等(即,输入的解释非常原始)。
很好的答案;我想补充一点,对于纯函数式语言,没有普遍认可的计算复杂性模型,而在不纯世界中,单位成本 RAM 机器是相对标准的(因此这使得比较事情变得更加困难)。另请注意,纯/不纯的 Lg(N) 差异的上限可以通过查看纯语言中数组的实现非常容易地直观地解释(每次操作花费 lg(n)(并且您会获得历史记录)) .
重要的一点:如果您最终要(自动或手动)将其转换为更高效的非纯代码,那么费力地将纯功能规范转换为更复杂高效的纯功能实现几乎没有什么好处。如果您可以将杂质保存在笼子中,例如通过将其锁定在无外部副作用的功能中,杂质就不是什么大问题了。
M
Matthias

确实有几种算法和数据结构没有已知的渐近有效的纯函数解决方案(在纯 lambda 演算中可以实现的一种),即使是惰性的也是如此。

上述联合发现

哈希表

数组

一些图算法

...

然而,我们假设在“命令式”语言中,对内存的访问是 O(1),而理论上不可能如此渐近(即对于无限的问题大小),并且在一个巨大的数据集中访问内存总是 O(log n) ,可以用函数式语言来模拟。

此外,我们必须记住,实际上所有现代函数式语言都提供可变数据,Haskell 甚至在不牺牲纯度的情况下提供它(ST monad)。


如果数据集适合物理内存,则访问它是 O(1),因为可以找到读取任何项目的时间量的绝对上限。如果数据集没有,那么您正在谈论 I/O,这将是迄今为止的主导因素,但是程序是编写的。
好吧,当然我说的是访问外部存储器的 O(log n) 操作。但是,无论如何我说的是 bs:外部存储器也可以是 O(1) 可寻址的......
我认为与函数式编程相比,命令式编程获得的最大好处之一是能够保存对一个状态的许多不同方面的引用,并生成一个新状态,以便所有这些引用都指向新状态的相应方面。使用函数式编程需要将直接取消引用操作替换为查找操作,以找到当前整体状态的特定版本的适当方面。
即使是指针模型(O(log n) 内存访问,松散地说)在非常大的尺度上也不现实。光速限制了不同的计算设备可以相互通信的速度,而currently believed可以在给定区域中保存的最大信息量受其表面积的限制。
P
Pascal Cuoq

This article 声称 the union-find algorithm 的已知纯函数实现都比他们发布的具有更差的渐近复杂性,后者具有纯函数接口但在内部使用可变数据。

事实上,其他答案声称永远不会有任何区别,例如,纯函数式代码的唯一“缺点”是它可以并行化,这让您了解函数式编程社区在这些问题上的知情/客观性.

编辑:

下面的评论指出,关于纯函数式编程的优缺点的有偏见的讨论可能不是来自“函数式编程社区”。好点子。也许我看到的倡导者只是,引用评论,“文盲”。

例如,我认为这个 blog post 是由可以说是函数式编程社区代表的人编写的,并且由于它是“惰性评估点”的列表,因此它是提及任何缺点的好地方懒惰和纯粹的函数式编程可能有。一个好的地方应该是代替以下(技术上正确,但偏向于不好笑)解雇:

如果严格的函数在严格语言中具有 O(f(n)) 复杂度,那么在惰性语言中它也具有 O(f(n)) 复杂度。为什么要担心? :)


B
Brian

使用固定的内存使用上限,应该没有区别。

证明草图:给定内存使用的固定上限,应该能够编写一个虚拟机来执行命令式指令集,其渐近复杂度与您实际在该机器上执行一样。之所以如此,是因为您可以将可变内存作为持久数据结构进行管理,提供 O(log(n)) 读取和写入,但是在内存使用量的上限固定的情况下,您可以拥有固定数量的内存,从而导致这些衰减到 O(1)。因此功能实现可以是运行在虚拟机功能实现中的命令式版本,因此它们应该具有相同的渐近复杂度。


内存使用的固定上限不是人们如何分析这类事情;你假设一个任意大但有限的内存。在实现算法时,我对它如何从最简单的输入扩展到任意输入大小感兴趣。如果你为内存使用设置了一个固定的上限,为什么不也为允许计算花费多长时间设置一个固定的上限,并说一切都是 O(1)?
@Brian Campbell:这是真的。我只是建议,如果您愿意,在实践中的许多情况下,您可以忽略常数因子的差异。在内存和时间之间进行折衷时,仍然需要注意差异,以确保使用 m 倍以上的内存将运行时间减少至少 log(m) 的一个因子。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅