如何在 Clojure 中递归展平任意嵌套的向量和映射?

Posted

技术标签:

【中文标题】如何在 Clojure 中递归展平任意嵌套的向量和映射?【英文标题】:how can I recursively flatten arbitrarily nested vectors and maps in Clojure? 【发布时间】:2016-09-11 17:09:20 【问题描述】:

我正在尝试使用递归来遍历 Clojure 中任意嵌套的向量和映射树,并返回一个仅包含关键字的向量,包括顶部。

所以下面的示例数据应该返回:

[: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)

【问题讨论】:

为什么一开始就有这样一个任意嵌套的序列?通常,只生成一开始就正确形成的数据,而不是对其应用这种奇怪的修复,这通常是可能的,而且更好。 这是一个编码挑战 :) 如果这是一个编码挑战,而我给了你答案,我是否因解决编码挑战而获得奖励? 我实际上已经解决了这个挑战,但我个人正在寻找一个更通用的解决方案来解决我上面概述的问题。我可以通过接受您的回答来表扬您。 【参考方案1】:

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

果然有

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

但是,如果您这样做是为了了解它实际上是如何发生的,您可以在 github 上查看函数的源代码(flatten stuff),其中 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) 

【讨论】:

flatten 仅适用于序列,而地图则不然。您可以使用(flatten (seq my-map)) 作为一种解决方法,但它的深度不会超过一级... @kurofune 这不太正确; flatten 适用于 sequential? 的任何内容,其中包括可能不一定是序列的事物(如向量)。 @SamEstep 感谢您的花絮【参考方案2】:

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

另见postwalk-demo:http://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 结果) => [: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]

【讨论】:

这是一个很好的解决方案,但我实际上是在尝试使用递归来解决问题。我确信有一种非常简单的方法可以做到这一点。【参考方案3】:

看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))))

【讨论】:

【参考方案4】:

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

(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)

【讨论】:

我也非常喜欢 tre-seq 解决方案,它可以处理大量、深度嵌套的输入,但递归解决方案正是我所寻找的。谢谢!

以上是关于如何在 Clojure 中递归展平任意嵌套的向量和映射?的主要内容,如果未能解决你的问题,请参考以下文章

将嵌套列表展平为 1 深列表

如何在keras中递归扩展/解析/展平嵌套模型?

在 Clojure 中将嵌套向量减少为另一个向量

是否有展平嵌套元素列表的功能?

如何使用 flatten_json 递归地展平嵌套的 JSON

在模板中调整递归嵌套向量的大小