我如何在 Clojure 中递归地展平任意嵌套的向量和地图?

how can I recursively flatten arbitrarily nested vectors and maps in Clojure?

我正在尝试使用递归遍历 Clojure 中任意嵌套的向量和映射树,以及 return 仅包含关键字的向量,包括顶部。

所以下面的样本数据应该return:

[:top :top :top :top :top :top :top :top :bottom :bottom :bottom :bottom :bottom :bottom :bottom :bottom :bottom :bottom :bottom :bottom],

但排名不分先后。

有人可以帮我正确地做到这一点吗?以下是我目前所拥有的。

(def sample [{:top {:top {:top [:bottom {:top {:top [:bottom :bottom :bottom]}} :bottom :bottom :bottom]}}}, 
                        {:top {:top [:bottom :bottom :bottom]}}, 
                        {:top [:bottom :bottom]}])

(defn make-flat [graph]
  (loop [graph graph]
    (if (every? keyword? graph) graph
      (recur (into graph (flatten (seq (first (filter #(not (keyword? %)) graph)))))))))

(make-flat sample)

我怀疑 clojure.clj = clojure 基础库中已经有一个函数可以执行此操作

果然有

https://clojuredocs.org/clojure.core/flatten

但是,如果您这样做是为了了解它实际上是如何发生的,您可以查看 github 上的源代码以了解函数(展平东西),其中 stuff 是您想要展平的东西。

请注意,对于地图,您必须通过调用 seq 来使用解决方法。

 (seq the-map-you-wanna-flatten-eventually)

user=>(展平{:name "Hubert" :age 23}) ()

; Workaround for maps

user=> (flatten (seq {:name "Hubert" :age 23}))
(:name "Hubert" :age 23) 

你可能想看看 postwalk 这里 http://clojuredocs.org/clojure.walk/postwalk

另见 postwalk-demohttp://clojuredocs.org/clojure.walk/postwalk-demo

这是一个工作程序:

(ns clj.core
  (:use tupelo.core)
  (:require [clojure.walk :refer [postwalk]] )
)

(def result (atom []))

(defn go [data]
  (postwalk (fn [it]
              (spyx it)
              (when (keyword? it)
                (swap! result append it))
              it)
            data))

(newline)
(spyx (go {:a 1 :b {:c 3 :d 4}}))
(spyx @result)

结果:

it => :a
it => 1
it => [:a 1]
it => :b
it => :c
it => 3
it => [:c 3]
it => :d
it => 4
it => [:d 4]
it => {:c 3, :d 4}
it => [:b {:c 3, :d 4}]
it => {:a 1, :b {:c 3, :d 4}}
(go {:a 1, :b {:c 3, :d 4}}) => {:a 1, :b {:c 3, :d 4}}
(clojure.core/deref result) => [:a :b :c :d]

使用你的数据,最终输出是:

(clojure.core/deref result) => [:top :top :top :bottom :top :top :bottom :bottom :bottom :bottom :bottom :bottom :top :top :bottom :bottom :bottom :top :bottom :bottom]

这是一个简单的递归解决方案:

(def mm {:a 1 :b {:c 3 :d 4}})

(defn accum 
  [it]
  (spy :msg "accum" it)
  (when (keyword? it)
    (swap! result append it)))

(defn walk [data]
  (spy :msg "walk" data)
  (cond
    (coll?  data)  (mapv walk data)
    :else          (accum data)))

(newline)
(reset! result [])
(walk mm)
(spyx @result)

输出:

walk => {:a 1, :b {:c 3, :d 4}}
walk => [:a 1]
walk => :a
accum => :a
walk => 1
accum => 1
walk => [:b {:c 3, :d 4}]
walk => :b
accum => :b
walk => {:c 3, :d 4}
walk => [:c 3]
walk => :c
accum => :c
walk => 3
accum => 3
walk => [:d 4]
walk => :d
accum => :d
walk => 4
accum => 4
(clojure.core/deref result) => [:a :b :c :d]

查看flatten的来源:

(defn flatten
  "Takes any nested combination of sequential things (lists, vectors,
  etc.) and returns their contents as a single, flat sequence.
  (flatten nil) returns an empty sequence."
  {:added "1.2"
   :static true}
  [x]
  (filter (complement sequential?)
          (rest (tree-seq sequential? seq x))))

您现在只需将 sequential? 更改为 coll? 即可包含地图。此外,如果您只想获取关键字,您还可以添加 every-pred:

(defn flatten' [x]
  (filter (every-pred (complement coll?) keyword?)
          (rest (tree-seq coll? seq x))))

如果您的数据嵌套不是很深(比如下百层),您可以简单地使用递归:

(defn my-flatten [x]
  (if (coll? x)
    (mapcat my-flatten x)
    [x]))

回复:

user> (my-flatten sample)
(:top :top :top :bottom :top :top :bottom :bottom :bottom 
 :bottom :bottom :bottom :top :top :bottom :bottom 
 :bottom :top :bottom :bottom)

否则我会同意 tree-seq 在这里是非常好的变体:

user> (filter keyword? (tree-seq coll? seq sample))
(:top :top :top :bottom :top :top :bottom :bottom 
 :bottom :bottom :bottom :bottom :top :top :bottom 
 :bottom :bottom :top :bottom :bottom)