我有一些地图
(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
进行尾调用优化?
答案 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来试试。