如果我的结构是
{ :a :A
:b :B
:c {
:d :D
}
:e {
:f {
:g :G
:h :H
}
}
}
我想获得一个名为keys-in
的函数,它返回类似于:
[[:a] [:b] [:c :d] [:e :f :g] [:e :f :h]]
所以我可以这样做:
(not-any? nil? (map #(get-in my-other-map %1) (keys-in my-map)))
因此,我可以确定my-other-map
具有与my-map
答案 0 :(得分:13)
(defn keys-in [m]
(if (map? m)
(vec
(mapcat (fn [[k v]]
(let [sub (keys-in v)
nested (map #(into [k] %) (filter (comp not empty?) sub))]
(if (seq nested)
nested
[[k]])))
m))
[]))
;; tests
user=> (keys-in nil)
[]
user=> (keys-in {})
[]
user=> (keys-in {:a 1 :b 2}))
[[:a] [:b]]
user=> (keys-in {:a {:b {:c 1}}})
[[:a :b :c]]
user=> (keys-in {:a {:b {:c 1}} :d {:e {:f 2}}})
[[:a :b :c] [:d :e :f]]
答案 1 :(得分:8)
(defn keys-in [m]
(if (or (not (map? m))
(empty? m))
'(())
(for [[k v] m
subkey (keys-in v)]
(cons k subkey))))
答案 2 :(得分:4)
强制性拉链版
(require '[clojure.zip :as z])
(defn keys-in [m]
(letfn [(branch? [[path m]] (map? m))
(children [[path m]] (for [[k v] m] [(conj path k) v]))]
(if (empty? m)
[]
(loop [t (z/zipper branch? children nil [[] m]), paths []]
(cond (z/end? t) paths
(z/branch? t) (recur (z/next t), paths)
:leaf (recur (z/next t), (conj paths (first (z/node t)))))))))
答案 3 :(得分:4)
如果您不需要懒惰的结果而只想快速,请尝试使用(defn keypaths
([m] (keypaths [] m ()))
([prev m result]
(reduce-kv (fn [res k v] (if (map? v)
(keypaths (conj prev k) v res)
(conj res (conj prev k))))
result
m)))
。
get-in
如果您还想支持向量索引(与update-in
或associative?
一样),请使用map?
代替(defn kvpaths-all2
([m] (kvpaths-all2 [] m ()))
([prev m result]
(reduce-kv (fn [res k v] (if (associative? v)
(let [kp (conj prev k)]
(kvpaths-all2 kp v (conj res kp)))
(conj res (conj prev k))))
result
m)))
进行测试。如果你想要中间路径,你也可以联合起来。这是一个变种:
{{1}}
答案 4 :(得分:2)
你可以很容易地用clojure.zip或tree-seq构建它,虽然我更喜欢prismatic.schema库来验证嵌套映射的结构
user> (def my-data-format
{:a Keyword
:b Keyword
:c {:d Keyword}
:e {:f {:g Keyword
:h Keyword}}})
#'user/my-data-format
user> (def some-data
{:a :A
:b :B
:c {:d :D}
:e {:f {:g :G
:h :G}}})
#'user/some-data
user> (schema/validate my-data-format some-data)
{:a :A, :c {:d :D}, :b :B, :e {:f {:g :G, :h :G}}}
user> (def some-wrong-data
{:a :A
:b :B
:c {:wrong :D}
:e {:f {:g :G
:h :G}}})
#'user/some-wrong-data
user> (schema/validate my-data-format some-wrong-data)
ExceptionInfo Value does not match schema:
{:c {:d missing-required-key,
:wrong disallowed-key}}
schema.core/validate (core.clj:132)
答案 5 :(得分:2)
得到了类似的问题,目前的解决方案并不满意:
“天真”的递归方法
(require '[clojure.set :as set])
(defn all-paths
([m current]
;; base case: map empty or not a map
(if (or (not (map? m)) (empty? m))
#{current}
;; else: recursive call for every (key, value) in the map
(apply set/union #{current}
(map (fn [[k v]]
(all-paths v (conj current k)))
m))))
([m]
(-> m (all-paths []) (disj []))))
(all-paths {:a 1
:b 2
:c {:ca 3
:cb {:cba 4
:cbb 5}}
:d {:da 6
:db 7}})
=> #{[:a] [:b] [:c] [:d] [:c :ca] [:c :cb] [:d :da] [:d :db] [:c :cb :cba] [:c :cb :cbb]}
答案 6 :(得分:2)
以下是使用Specter的解决方案(没有中间路径)。他们是Spectre的作者Nathan Marz,来自Spectre Slack频道的conversation(获得他的许可)。我对这些定义不予置评。
简单版本:
(defn keys-in [m]
(let [nav (recursive-path [] p
(if-path map?
[ALL (collect-one FIRST) LAST p]
STAY))]
(map butlast (select nav m))))
更高效的版本:
(defn keys-in [m]
(let [nav (recursive-path [] p
(if-path map?
[ALL
(if-path [LAST map?]
[(collect-one FIRST) LAST p]
FIRST)]))]
(select nav m)))
我对这些定义中发生的事情的非正式解释:
在简单版本中,由于顶级参数是地图,if-path map?
会将其传递给括号中的第一个导航器集合。这些以ALL
开头,这里说这是为地图中的每个元素做的其余部分。然后,对于地图中的每个MapEntry
,(collect-one FIRST)
表示将其第一个元素(键)添加到将其最后一个元素(val)再次传递给if-path
的结果中。 p
绑定recursive-path
作为对同一recursive-path
表达式的引用。通过这个过程,我们最终得到一个非地图。返回并停止对该分支的处理;那是STAY
的意思。但是,最后返回的东西不是其中一个键;这是终端val。所以我们最终得到了每个序列中的叶子。要删除它们,请在整个结果上映射butlast
。
第二个版本通过仅递归到MapEntry
中的val来避免这最后一步,如果该val本身就是一个映射。这就是内部if-path
所做的事情:[LAST map?]
获取最后一个元素,即MapEntry
生成的当前ALL
的val,并将其传递给map?
。
我使用Criterium来测试这个页面上不返回中间路径的所有关键路径函数,加上一个由answer to another question部分的noisesmith测试。对于3级,3级每层级映射和6级,6级密钥/级别映射,miner49r的版本和第二个更快的Spectre版本具有相似的速度,并且比任何其他版本快得多。
按照以下顺序在3级,3级/ 3级(27路径)地图上进行计时:
vec
):243.756275μs按照以下顺序在6级,每级6个键(6 ^ 6 = 46656路径)上进行计时:
vec
):839.266448 ms 所有调用都包含在doall
中,以便实现惰性结果。由于我doall
,我在Alex Miller的定义中取出vec
包装。
有关时间安排的详细信息,请参见here。测试代码为here。
(简单的Specter版本比速度更快的版本慢,因为使用map butlast
来去除叶子值。如果删除了这个步骤,那么简单的Spectre定义的时间与第二个类似定义。)
答案 7 :(得分:1)
我的这个答案只是为了说明如何不这样做,因为它仍然是程序性的。
(defn keys-in [data] (genkeys [] data))
(defn genkeys [parent data]
(let [mylist (transient [])]
(doseq [k (keys data)]
(do
(if ( = (class (k data)) clojure.lang.PersistentHashMap )
(#(reduce conj! %1 %2) mylist (genkeys (conj parent k ) (k data) ))
(conj! mylist (conj parent k ) )
)))
(persistent! mylist)))
答案 8 :(得分:1)
这是一个基于lazy-seq返回所有键(不仅仅是终端键)的实现:
(defn keys-in
([m] (if (map? m) (keys-in (seq m) [])))
([es c]
(lazy-seq
(when-let [e (first es)]
(let [c* (conj c (key e))]
(cons c* (concat (if (map? (val e)) (keys-in (seq (val e)) c*))
(keys-in (rest es) c))))))))
答案 9 :(得分:0)
为个人项目开展类似工作,这是我天真的实施:
(defn keys-in
[m parent-keys]
(mapcat (fn [[k v]]
(if (map? v)
(keys-in v (conj parent-keys k))
(vector (conj parent-keys k v))))
m))
从repl:
中使用它(keys-in <your-map> [])
奇特的方式:
(map (comp vec drop-last) (keys-in <your-map> []))
答案 10 :(得分:0)
Here是已知集合类型(包括地图)的通用解决方案(在自述页面上查找“关键路径”以获取用法示例)。
它还处理混合类型(顺序类型,映射和集合),并且API(协议)可以扩展为其他类型。