xml – 从大型Clojure树结构中的延迟序列中删除元素,避免头部保留

问题描述

为了在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项的整棵树都适合记忆.

在处理完树之后,我想得到一个新树,其中所有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)))

(Gist available on GitHub)

第二个示例将崩溃(取决于可用内存,可能需要调整二级项目的数量).映射过
:所有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.

解决方法

问题是你的2级节点都有指向相同的内存中延迟序列的指针,然后你多次映射该序列.如果您只是在第一个和第二个节点上创建了有效运行的进程,那么您将遇到同样的问题 – 节点数量并不重要,因为您使用任意两个节点来清空堆.在实际应用程序中,您从数据库文件或其他任何地方读取这些节点,它们将指向不同的对象,您可以依次对它们进行懒惰处理.

如果您生成更具代表性的示例数据(即相同数据但没有结构共享),则可以在处理每个节点时对其进行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个不同对象的函数.而不是返回一个节点,它们返回一个函数,当被调用时,它生成该节点的副本;这使得它们可以像原始版本一样组合,只需要在外层进行一次额外的函数调用.如果你实际上已经拥有了不同的对象,我怀疑你会在一个真实的应用程序中,你可以只使用你写的原始函数.

相关文章

php输出xml格式字符串
J2ME Mobile 3D入门教程系列文章之一
XML轻松学习手册
XML入门的常见问题(一)
XML入门的常见问题(三)
XML轻松学习手册(2)XML概念