Si ma structure est
(not-any? nil? (map #(get-in my-other-map %1) (keys-in my-map)))
11 Réponses :
Vous pouvez construire cela avec clojure.zip ou arbre-seq assez facilement, bien que je préfère fermement le prismatic.schema A> Bibliothèque de vérification de la structure des cartes imbriquées 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)
Des fermetures à glissière et de l'arbre-SEQ semblent tous les deux comme de très mauvaises solutions à ce problème. Je me demande comment vous envisagez de le faire.
Utilisateur> (filtre SEQ (Carte # (si (mappe?%) (KEYS%)) (Carte des arbres SEQ? Vals Certains-Data))) ((: A: C: B: E) (: f) (: g: h)) imprime d'abord la touche de touches et fait une fonction d'égalité fin en une ligne à ces fins. Bien que toutes ces solutions soient moins favorables pour la validation des données.
@Amalloy j'ai obligé avec une solution de fermeture à glissière. Ce n'est pas joli, mais ce n'est pas mauvais en soi si vous êtes paranoïaque sur la consommation de pile pour une carte imbriquée i>.
(defn keys-in [m]
(if (or (not (map? m))
(empty? m))
'(())
(for [[k v] m
subkey (keys-in v)]
(cons k subkey))))
@AlexMiller qui n'est pas exactement faux - (get-in {} '()) code> renvoie {} code>, car la clé de clé vide est la seule et unique valide keyseq dans {} code>. Mais je suppose que c'est incompatible: () code> devrait être présent comme une réponse pour toutes les cartes ou pour aucun.
(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]]
Hors de curiosité, comment pouvons-nous modifier cela afin que les clés qui n'ont pas de cartes imbriquées renvoient la clé au lieu d'un vecteur avec la clé à l'intérieur? Par exemple, (touches-in {: A 1: B {: C 3: D 4}}) code> => [: A [: B: B] [: B: D] ] code>
Cela pourrait-il être étendu à avoir les clés d'un tableau aussi?
Cette réponse de la mienne est juste pour illustrer la façon dont il ne faut pas le faire car il est toujours procédural.
Il ne produit pas non plus la réponse que vous avez demandée, BTW: (touches-in dra); => [[[[[: A] [: B] [: C] [: E]] code>, où < Code> DRA code> est défini comme exemple de la structure de données d'origine exemple: (DEL DRA {: A: A: B: B: C {: D: D}: E {: F {: G: G: H: h}}}) code>
Version de zippers obligatoire
(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)))))))))
Voici une implémentation qui renvoie toutes les touches (non seulement les touches de terminal) basées sur 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))))))))
Vous avez une question similaire, n'était pas satisfaite par les solutions actuelles:
approche récursive "naive" p>
Naïf ou non, cette solution qui fournit une clé pour chaque clavier, les solutions ci-dessus manquent les chemins de clé [: C] [: D].
Si vous n'avez pas besoin d'un résultat paresseux et que vous voulez juste être rapide, essayez d'utiliser réduction-kv code>. (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)))
La question initiale a demandé des intermédiaires, la variante fait cela, merci!
J'ai révisé mon ancienne réponse pour utiliser un accumulateur qui évite de copier. Cela le rend significativement plus rapide.
Voici des solutions (sans chemins intermédiaires) à l'aide de spectre . Ils sont par Nathan Marz, auteur du spectre, d'un conversation sur le Spectre Slack Channel (avec sa permission). Je ne revendique aucun crédit pour ces définitions.
Version simple: P>
(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)))
Travailler sur quelque chose de similaire pour un projet personnel et c'est ma mise en œuvre naïve: Utilisez-le à partir du repli: p> Fully Way: P> (map (comp vec drop-last) (keys-in <your-map> []))
ici est une solution générique pour les types de collecte connus, y compris des cartes (rechercher "Pistes clés" sur la page README pour des exemples d'utilisation). P>
Il gère également des types mélangés (types séquentiels, cartes et ensembles) et l'API (protocoles) peut être étendu à d'autres types. P>
Vraiment de bonnes réponses ici.
Dans un commentaire antérieur, j'ai signalé que plusieurs des fonctions données ici sont également rapides sur de petites cartes intégrées, selon critère. Je pense maintenant que ma méthode de test était défectueuse et j'ai supprimé ce commentaire.