【问题标题】:Help me understand how the conflict between immutability and running time is handled in Clojure帮助我了解在 Clojure 中如何处理不变性和运行时间之间的冲突
【发布时间】:2010-09-09 22:10:22
【问题描述】:

Clojure 真的激起了我的兴趣,我开始阅读它的教程: http://java.ociweb.com/mark/clojure/article.html

考虑一下“Set”下提到的这两行:

(def stooges (hash-set "Moe" "Larry" "Curly")) ; not sorted
(def more-stooges (conj stooges "Shemp")) ; -> #{"Moe" "Larry" "Curly" "Shemp"}

我的第一个想法是第二次操作应该需要恒定的时间才能完成;否则,函数式语言可能比面向对象的语言没有什么好处。可以很容易地想象需要从 [几乎] 空集开始,然后在我们进行的过程中填充和缩小它。因此,我们可以将新结果重新分配给自己,而不是将新结果分配给更多人。

现在,由于函数式语言的奇妙承诺,副作用不再值得关注。所以,集合stoogesmore-stooges 永远不能相互叠加。因此,more-stooges 的创建要么是线性操作,要么它们共享一个公共缓冲区(如 Java 的 StringBuffer),这似乎是一个非常糟糕的主意并且与不变性相冲突(随后 stooges 可以删除一个元素 -一个)。

我可能在这里重新发明了一个轮子。似乎hash-setclojure 中的性能更高,当您从最大数量的元素开始然后一次删除它们直到空集,而不是从空集开始并一次增长一个.

上面的例子可能看起来不太实用,或者有变通方法,但是像 Java/C#/Python/等面向对象的语言。一次增加或缩小一个或几个元素,同时速度也很快。

保证(或只是承诺?)不变性的[功能]语言将无法快速增长集合。是否有另一种可以使用的成语以某种方式帮助避免这样做?

对于熟悉Python 的人,我会提到集合理解与等效循环方法。两者的运行时间略有不同,但这与CPython、解释器的相对速度有关,而不是源于复杂性。我看到的问题是集合理解通常是更好的方法,但并不总是最好的方法,因为可读性可能会受到很大影响。

如果问题不清楚,请告诉我。

【问题讨论】:

  • @Mike Dunlavey,你是一个国际象棋玩家,Capuchino-drinkin ilitism。
  • Awe shucks (blush) :-) 刚查了“ilitist”。这并不意味着一个文盲并以此为荣的人。 “回肠炎”是一种肠道疾病。我敢肯定你也不是那个意思:)

标签: clojure immutability hashset performance


【解决方案1】:

核心不可变数据结构对我来说也是该语言最迷人的部分之一。他们非常愿意回答这个问题,Rich 在这段视频中做得非常出色:

http://blip.tv/file/707974

核心数据结构:

  • 实际上是完全不可变的
  • 旧副本也是不可变的
  • 旧副本的性能不会降低
  • 访问是常量(实际上是有界
  • 都支持高效的追加、连接(列表和序列除外)和截断

他们是怎么做到的???

  • 秘密:它是几乎所有的树在引擎盖下(实际上是树)。

但是如果我真的想就地编辑东西怎么办?

  • 您可以使用 clojure 的 transients 就地编辑结构,然后在准备好共享时生成不可变版本(在恒定时间内)。

作为一个小背景:Trie 是一棵树,其中键的所有公共元素都被提升到树的顶部。 clojure 中的集合和映射使用 trie,其中索引是您要查找的键的哈希值。然后它将散列分解成小块,并将每个块用作一级散列树的键。这允许共享新旧地图的公共部分,并且访问时间是有限的,因为只能有固定数量的分支,因为在输入中使用的散列具有固定的大小。

使用这些哈希尝试还有助于防止在许多其他持久性数据结构使用的重新平衡期间出现大幅减速。所以你实际上会得到相当恒定的挂钟访问时间。

我真的推荐(相对较短)_书:Purely Functional Data Structures 在其中,他涵盖了许多非常有趣的结构和概念,例如“消除摊销”以允许队列的真正恒定时间访问。以及诸如惰性持久队列之类的东西。作者甚至在pdf here提供免费副本

【讨论】:

  • “trei”与 trie 不同还是只是一个错字?
  • its trei(发音为 try)我只是遇到了一点键盘问题 :)
  • 来自维基百科文章:“术语 trie 来自'检索'。根据词源,发明者爱德华·弗雷德金(Edward Fredkin)将其发音为“树”。但是,其他作者将其发音为“尝试”。”
【解决方案2】:

Clojure 的数据结构是持久的,这意味着它们是不可变的,但使用结构共享来支持有效的“修改”。请参阅 Clojure 文档中关于 immutable data structures 的部分以获得更详尽的解释。特别是,它指出

具体来说,这意味着无法使用完整副本创建新版本,因为这需要线性时间。不可避免地,持久性集合是使用链接数据结构实现的,因此新版本可以与之前的版本共享结构。

Theseposts,以及一些Rich Hickey's talks,很好地概述了持久数据结构的实现。

【讨论】:

  • 删除有多复杂?假设我有 10 个不同的集合或字典,它们是增量创建的。然后我从第一个 set/dict (仅包含一个元素的那个)中删除所有 10 个共有的节点。 clojure 会做什么,需要多长时间?
  • 集合/字典是增量构建还是一次性构建都没有关系。删除与添加一样有效——它实际上是一个恒定时间操作。
猜你喜欢
  • 2018-05-22
  • 2013-08-15
  • 2011-09-23
  • 1970-01-01
  • 2017-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多