【发布时间】:2015-09-19 11:40:30
【问题描述】:
问题描述
对于在 Clojure 中处理大型数据结构,惰性序列提供了一种很好的惯用方法。一个人需要小心避免头部 不过保留。
我很难处理这样的大树结构:
R Root
__________|____________________
A B C, D, E, ... 1st Level Children
_______|_______ _______|_______
X Y Y ... Y X Y X Y Y ... Y X Y 2nd Level Children
- 所有节点都是带有键
:content的映射。任何:content的值都是包含该节点所有子节点的惰性序列。 - 整棵树不适合内存。第 2 层的
Y项目太多。 - 不包括
Y项的整个树都适合内存。
处理完树后,我想得到一棵新树,其中所有Y 节点都被删除:
R
______|__________________
A B C, D, E, ...
_____|___ _____|___
X X ... X X X ... X
示例代码及进一步说明
;; Generating example data
;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn root [content]
{:tag :root :content content})
(defn lazy-elements [n tag content]
(lazy-seq (repeat n {:tag tag :content content})))
(defn level-1 [content]
(lazy-elements 3 :A content))
(defn level-2 [n]
(concat (lazy-elements 10 :X '(:leaf))
(lazy-elements n :Y '(:leaf))))
(defn remove-nodes [node]
(remove #(= (:tag %) :Y) node))
;; Illustrating usage
;;;;;;;;;;;;;;;;;;;;;
;; runs and runs and runs... and eventually returns correctly
(defn valid-run []
(->> (root (level-1 (level-2 1e8)))
:content
first
:content
remove-nodes))
;; Does not terminate properly, runs out of memory
(defn invalid-run []
(->> (root (level-1 (level-2 1e8)))
:content
(map :content) ; source of head retention
(map remove-nodes)))
第二个示例将崩溃(根据可用内存,可能需要调整 2 级项目的数量)。映射在
:content 的所有 1 级项目引入了一个参考,该参考在循环浏览所有内容项目时会导致头部保持问题
删除不需要的 :Y 项目。
我能够使用来自valid-run 之类的数据,将其置于保持可变状态的 var 中,为所有相关节点执行此操作
然后再次将所有数据拼接在一起。但我对这种方法非常不满意,因为必须依赖可变性
最后必须使用一些非常命令式的代码来合并数据(例如,通过列表的索引运行)。
问题
如何以函数式、声明式的风格实现这一点?理想情况下,我希望避免使用可变状态以及 过于迫切(例如,使用索引等将集合拼接在一起)。
资源
以下文章和 sn-ps 是关于该问题各个方面的有趣读物:
- XML manipulation in Clojure
- Rich Hickey on incidental complexity of head retention
- Related Clojure documentation
- Head retention funkiness in a nutshell
更多背景知识
最终我需要它来处理大型 XML 文件。大意味着> 1GB并且将其解析成树将不适用于可用 记忆。从那个 XML,我想将一些元素放入存储桶 A(假设是一个数据库表),并将 XML 树的所有其余部分放入存储桶 B. 提取的子树当然应该保留 XML 结构。
除了将 XML 解析为树之外,我还可以将 XML 处理为事件流,例如通过data.xml/source-seq。然而, 这将意味着失去 XML 树语义。会工作,但不是很漂亮。但也许还有其他处理方法 首先是 XML。
【问题讨论】:
-
您正在构建一棵新树。你能在创作的时候把它写出来,然后释放你已经写的东西吗?在第 i 层,您有一个惰性节点序列。选择下一个节点,然后进入它。一旦你完成了那个节点,写出它的替换树并释放它和它下面的所有树。降序意味着迭代该过程,除了写作部分。这个想法是仅将您正在检查的子树保存在内存中,并写出并忘记您已经处理过的内容,懒惰地将新的子树带入内存。不确定这是否一切正常。
-
另一个:你只从叶节点中清除 Y,对吧?在同一级别?如果是,则构造一个叶节点序列序列,代表同一节点下的叶节点。这些应该有标识符,以便您可以在剥离 Y 后将它们附加回它们的父节点。即:您首先遍历树只是为了构建叶节点序列的序列。然后你删除Y。然后您再次遍历树以创建具有新叶节点集的新树。写出子树以防止新树全部在内存中。同样,不确定...
-
我的建议可能是可怕的想法。就在我的脑海中。
-
我不得不说我还没有完全理解这个问题,但是我使用惰性拉链方法在 clojure 中的树上取得了成功,参考:josf.info/blog/2014/04/14/seqs-of-clojure-zippers 和 josf.info/blog/2014/04/14/seqs-of-clojure-zippers。这些使得处理树非常“实用”且不可变。
-
我只想指出这是一个很好的问题。一个有趣的技术问题,通过背景、示例甚至图表进行了很好的解释;和可运行的代码 sn-ps 来重现确切的问题。它唯一缺少的是一个示例堆栈跟踪,因此没有人必须在本地运行它才能看到发生的错误。
标签: xml clojure tree lazy-evaluation