你将如何调用尾部来优化构建树的Clojure函数?

时间:2017-07-01 22:41:33

标签: clojure tail-recursion

我有一些地图

(def m1 [{:a 1, :b 2, :c 0}
         {:a 1, :b 3, :c 0}
         {:a 1, :b 0, :c 2}
         {:a 1, :b 3, :c 1}
         {:a 1, :b 0, :c 3}])

我可以使用此函数递归分组

(defn group [ks coll]
  (if (empty? ks) coll
      (let [gs (group-by #(select-keys % [(first ks)]) coll)]
        (map (fn [[k v]] {k (group (rest ks) v)}) (dissoc gs {})))))

产生预期结果:

(group [:a :b :c] m1)

=>

({{:a 1} ({{:b 2} ({{:c 0} [{:a 1, :b 2, :c 0}]})}
          {{:b 3} ({{:c 0} [{:a 1, :b 3, :c 0}]}
                   {{:c 1} [{:a 1, :b 3, :c 1}]})}
          {{:b 0} ({{:c 2} [{:a 1, :b 0, :c 2}]}
                   {{:c 3} [{:a 1, :b 0, :c 3}]})})})

你怎么能重写这样的函数,因为它需要跟随多个路径,在最后一个位置有map,使用recur进行尾调用优化?

2 个答案:

答案 0 :(得分:0)

嗯,事实证明它非常复杂。我设法使用loop/recur实现了上面的内容,但我必须引入一堆辅助函数来保持它的可读性:

(defn group-recur [ks coll]
  (let [split-map (fn [m]
                    (->> m
                         (into [])
                         (map (partial apply hash-map))))
        dissoc-non-map (fn [m k]
                         (let [l (drop-last k)]
                           (if-not (map? (get-in m k))
                             (if (empty? l)
                               (dissoc m (last k))
                               (update-in m l dissoc (last k)))
                             m)))
        updater (fn [r k v]
                  (let [s (split-map k)]
                    (-> r
                        (dissoc-non-map (drop-last s))
                        (assoc-in s v))))]
  (loop [k ks
         h []
         r {}]
    (if (not-empty k)
      (let [all (conj h (first k))
            grouped (dissoc (group-by #(select-keys % all) coll) {})]
        (->> grouped
             (reduce-kv updater r)
             (recur (rest k)
                    all)))
      r))))

它返回一张地图树,而不是你上面使用的集合(我认为更符合逻辑)。基本前提是您传递结果图并在进行时更新它。这就是dissoc-non-map存在的原因,显然如果在更高级别上已存在另一个非地图值,则不能在地图中assoc-in

循环需要3个参数:仍要处理的键列表,我们当前正在处理的键/值对(即,如果你愿意,当前“树中的位置”)以及生成的映射树。

另一种(可能更简单的)实现可以传递两个映射:一个具有最终结果,另一个具有中间值。这应该不需要dissoc-non-map

答案 1 :(得分:0)

您的原始算法要求节点的当前兄弟节点在重复时保留为上下文,以便我们可以返回到重复到底部后处理这些兄弟节点。稍作修改,我们可以一次构建一个节点的完整路径,深度优先,忽略节点的关系。这允许更简单的尾调用递归。

Export JOSN

这是一个online Repl来试试。