Clojure数据结构转换

时间:2016-08-29 18:23:00

标签: list optimization clojure tree

我需要翻译具有这种结构的数组映射:

{A [(A B) (A C)], C [(C D)], B [(B nil)], D [(D E) (D F)]}

进入此等效列表:

'(A (B (nil)) (C (D (E) (F))))

我有这个功能,但不适用于深层结构:

(def to-tree (memoize (fn [start nodes]
     (list* start
          (if-let [connections (seq (nodes start))]
            (map #(to-tree (second %) nodes) connections))))))

但是,随着嵌套元素的增长,它会发出堆栈溢出错误。如何优化此功能,或者更确切地说,有没有办法使用walk或任何其他功能方法?

1 个答案:

答案 0 :(得分:4)

您提供的输入数据看起来很像邻接列表。您可以采取的一种方法是将数据转换为图形,然后从中创建树。

以下是使用loom处理图表的解决方案。此示例仅使用来自织机(loom.graph/digraph)的一个函数,因此如果添加依赖项不适合您,则可能构建类似的东西。

让我们首先从数据结构中创建有向图。

(defn adj-list
  "Converts the data structure into an adjacency list."
  [ds]
  (into {} (map
            ;; convert [:a [[:a :b] [:a :c]]] => [:a [:b :c]]
            (fn [[k vs]] [k (map second vs)])
            ds)))

(defn ds->digraph
  "Creates a directed graph that mirrors the data structure."
  [ds]
  (loom.graph/digraph (adj-list ds)))

一旦我们构建了图形,我们就想从图形的根节点生成树。在您的示例中,只有一个根节点(A),但实际上并没有将其限制为只有一个。

Loom存储图表中所有节点的列表,以及一组具有到图中给定节点的传入边缘的节点。我们可以使用它们来查找根节点。

(defn roots
  "Finds the set of nodes that are root nodes in the graph.

   Root nodes are those with no incoming edges."
  [g]
  (clojure.set/difference (:nodeset g)
                          (set (keys (:in g)))))

鉴于根节点,我们现在只需要为每个节点创建一个树。我们可以查询图表中与给定节点相邻的节点,然后递归地为这些节点创建树。

(defn to-tree [g n]
  "Given a node in a graph, create a tree (lazily).

   Assumes that n is a node in g."
  (if-let [succ (get-in g [:adj n])]
    (cons n (lazy-seq (map #(to-tree g %) succ)))
    (list n)))

(defn to-trees
  "Convert a graph into a collection of trees, one for each root node."
  [g]
  (map #(to-tree g %) (roots g)))

......就是这样!根据您的输入,我们可以生成所需的输出:

(def input {:a [[:a :b] [:a :c]] :c [[:c :d]] :b [[:b nil]] :d [[:d :e] [:d :f]]})
(first (to-trees (ds->digraph input))) ; => (:a (:c (:d (:e) (:f))) (:b (nil)))

以下是一些用于生成深层结构或具有多个根节点的结构的输入。

(def input-deep (into {} (map (fn [[x y z]] [x [[x y] [x z]]]) (partition 3 2 (range 1000)))))
(def input-2-roots {:a [[:a :b] [:a :c]] :b [[:b nil]] :c [[:c :d]] :e [[:e :b] [:e :d]]})

(to-trees (ds->digraph input-2-roots)) ; => ((:e (:b (nil)) (:d)) (:a (:c (:d)) (:b (nil))))

这种方法的一个很酷的事情是,它可以与无限嵌套数据结构一起使用,因为生成树是懒惰的。如果您尝试渲染树(因为它也无限嵌套),您将获得StackOverflowException,但实际生成它没有问题。

使用此方法最简单的方法是创建一个带循环的结构,如下例所示。 (请注意,:c节点是必需的。如果图中只有:a:b,则没有根节点!)

(def input-cycle {:a [[:a :b]] :b [[:b :a]] :c [[:c :a]]})

(def ts (to-trees (ds->digraph input-cycle)))
(-> ts first second first)        ;; :a
(-> ts first second second first) ;; :b

您可以使用loom.alg/dag?来测试此条件。