【问题标题】:Immutable queue in ClojureClojure 中的不可变队列
【发布时间】:2011-03-09 08:45:18
【问题描述】:

在 Clojure 中获取简单、高效的不可变队列数据类型的最佳方法是什么?

它只需要两个操作,通常语义的入队和出队。

我当然考虑过列表和向量,但我知道它们在结尾和开头的修改分别具有相对较差的性能(即 O(n) 或更差) - 所以不适合队列!

理想情况下,我想要一个适当的持久数据结构,其 O(log n) 用于入队和出队操作。

【问题讨论】:

  • 为了避免有人写关于如何使用 cons 列表来实现 push/pop 堆栈(就像我几乎所做的那样),不要忘记关于 queues 的问题。 :-)
  • 刚刚注意到在最新的 1.2 快照 Clojure Java 源代码中有一个名为 PersistentQueue 的类....可能是我自己问题的答案
  • 它从那以后就一直存在(刚刚检查了 1.1,但我认为它比那更旧)。请注意,默认情况下没有提供工厂函数或阅读器语法;使用clojure.lang.PersistentQueue/EMPTY 获取一个空实例。然后conjpoppeek 按他们应该的方式处理队列。参见例如我对这个问题的回答:stackoverflow.com/questions/2760017 一些用c.l.PQ 和Java 的LinkedBlockingQueue 编写的代码。
  • 酷,谢谢米哈尔!我想我一开始错过了它,因为 API 中没有简单的“队列”构造函数。也许我应该提交一个补丁:-)
  • PersistentQueue 确实是 Clojure 更严密保护的秘密之一。 ;-) 关于可能的与队列相关的 API 增强功能,请参阅 Clojure Dev 上的此线程:groups.google.com/group/clojure-dev/browse_thread/thread/… 请注意,现在这可能是一个非常低优先级的问题,新数字和所有...

标签: algorithm data-structures clojure queue immutability


【解决方案1】:

问题已解决 - 为其他可能觉得有用的人提供解决方案。

我发现 Clojure 有 clojure.lang.PersistentQueue 类可以满足需要。

你可以像这样创建一个实例:

(def x (atom clojure.lang.PersistentQueue/EMPTY))

据我所知,您目前需要使用 Java 互操作来创建实例,但正如 Michal 有用地指出的那样,您随后可以使用 peek、pop 和 conj。

【讨论】:

  • PersistentQueue 确实是您的最佳选择。为了将来参考,这里有一个总结了 clojure 数据结构的性能特征/保证的表格:innoq.com/blog/st/2010/04/clojure_performance_guarantees.html
  • @raxod502 在这种情况下使用原子有什么问题吗?
  • @dgellow 根据我的理解,在函数式编程语言中使用原子是一个不好的迹象,除非有充分的理由——一切通常都应该是不可变的。
  • @RadonRosborough,...但是,在 Var 中放置一个原子绝对比改变 Var 本身更好。 Which is to say, if you're going to use def (which isn't always necessary, granted!) to put a name to something other than a constant, then an appropriately-chosen reference type 通常是与之关联的正确事物。
  • @CharlesDuffy 你当然是对的。我发表评论的唯一原因是因为问题中没有任何内容表明需要 STM 或可变状态,所以我发现答案使用 atom 没有明显原因很奇怪。
【解决方案2】:

我使用下面的函数queue 来创建一个PersistentQueue。如果要打印和读取队列,您可能需要一个打印方法和一个数据读取器。

已经为 PersistentQueue 实现了常用的 Clojure 函数。

  • 偷看——拿到头
  • pop -- 返回一个没有头部的新 PersistentQueue
  • conj -- 将项目添加到尾部
  • 为空? -- 如果为空则为真
  • seq -- 内容为序列(列表)

    (defn queue
      ([] clojure.lang.PersistentQueue/EMPTY)
      ([coll] (reduce conj clojure.lang.PersistentQueue/EMPTY coll)))

    (defmethod print-method clojure.lang.PersistentQueue
      [q ^java.io.Writer w]
      (.write w "#queue ")
      (print-method (sequence q) w))

    (comment
       (let [*data-readers* {'queue #'queue}]
         (read-string (pr-str (queue [1 2 3])))))

【讨论】:

    【解决方案3】:

    Clojure 确实可以从队列文字中受益。这将比依赖 Java 互操作更简洁(并且更便携)。

    但是,滚动您自己的便携式持久队列并不难,只需使用列表等普通家居用品。

    将队列视为两个列表,一个提供队列的头部,另一个提供尾部。 enqueue 添加到第一个列表,dequeue 从后者弹出。大多数ISeq 函数都是简单实现的。

    可能唯一棘手的部分是当尾部为空并且您想要dequeue 时会发生什么。在这种情况下,头部列表为reversed 并成为新的尾部,空列表成为新的头部列表。我相信,即使有 reverse 的开销,enqueuedequeue 仍然是 O(1),尽管 k 当然会高于普通向量。

    快乐queueing!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-05
      • 2019-06-20
      • 1970-01-01
      • 2015-06-06
      • 2013-01-18
      相关资源
      最近更新 更多