为了在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
>所有节点都是带有键的地图:内容. any:content的值是该节点的所有子节点的延迟seq.
>整棵树不适合记忆.第二级有太多Y项目.
>不包括Y项的整棵树都适合记忆.
R ______|__________________ A B C,... _____|___ _____|___ 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)))
第二个示例将崩溃(取决于可用内存,可能需要调整二级项目的数量).映射过
:所有1级项目的内容都引入了一个引用,该引用会在循环浏览所有内容项时导致头部保留问题
删除不需要的:Y项.
我能够使用有效运行之类的数据,将其放入var保持可变状态,为所有相关节点执行此操作
之后再将所有数据拼接在一起.但由于不得不依赖于可变性,我对这种方法非常不满意
并且必须使用一些非常强制性的代码来最终合并数据(例如,通过列表的索引运行).
题
如何以功能性的声明式方式实现这一目标?理想情况下,我希望避免使用可变状态以及存在
太迫切(例如使用索引等将集合拼接在一起).
资源
以下文章和片段是有关该问题方面的有趣读物:
> 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.
解决方法
如果您生成更具代表性的示例数据(即相同数据但没有结构共享),则可以在处理每个节点时对其进行GC:
(defn root' [content] (fn [] {:tag :root :content (content)})) (defn lazy-elements' [n tag content] (repeatedly n (fn [] {:tag tag :content (content)}))) (defn level-1' [content] (fn [] (lazy-elements' 3 :A content))) (defn level-2' [n] (fn [] (concat (lazy-elements' 10 :X (fn [] '(:leaf))) (lazy-elements' n :Y (fn [] '(:leaf)))))) (defn remove-nodes [node] (remove #(= (:tag %) :Y) node)) (defn run [] (let [root-builder (root' (level-1' (level-2' 1e8)))] (->> (root-builder) :content (map :content) (map remove-nodes)))) user> (pprint (run)) (({:tag :X,:content (:leaf)} {:tag :X,:content (:leaf)}) ({:tag :X,:content (:leaf)}))
由于我们只是生成示例内容,因此我调整了所有节点构建器,而不是它们应该存储N个副本的对象,它们应该调用N次以获得N个不同对象的函数.而不是返回一个节点,它们返回一个函数,当被调用时,它生成该节点的副本;这使得它们可以像原始版本一样组合,只需要在外层进行一次额外的函数调用.如果你实际上已经拥有了不同的对象,我怀疑你会在一个真实的应用程序中,你可以只使用你写的原始函数.