10
votes

Comment puis-je obtenir les clés imbriquées d'une carte dans le clojure?

Si ma structure est

(not-any? nil? (map #(get-in my-other-map %1) (keys-in my-map)))


2 commentaires

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.


11 Réponses :


2
votes

3 commentaires

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 .



9
votes
(defn keys-in [m]
  (if (or (not (map? m))
          (empty? m))
    '(())
    (for [[k v] m
          subkey (keys-in v)]
      (cons k subkey))))

1 commentaires

@AlexMiller qui n'est pas exactement faux - (get-in {} '()) renvoie {} , car la clé de clé vide est la seule et unique valide keyseq dans {} . Mais je suppose que c'est incompatible: () devrait être présent comme une réponse pour toutes les cartes ou pour aucun.



16
votes
(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]]

2 commentaires

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}}) => [: A [: B: B] [: B: D] ]


Cela pourrait-il être étendu à avoir les clés d'un tableau aussi?



1
votes

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. xxx


1 commentaires

Il ne produit pas non plus la réponse que vous avez demandée, BTW: (touches-in dra); => [[[[[: A] [: B] [: C] [: E]] , où < Code> DRA 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}}})



4
votes

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


0 commentaires

1
votes

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


0 commentaires

2
votes

Vous avez une question similaire, n'était pas satisfaite par les solutions actuelles:

approche récursive "naive" xxx


1 commentaires

Naïf ou non, cette solution qui fournit une clé pour chaque clavier, les solutions ci-dessus manquent les chemins de clé [: C] [: D].



4
votes

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


2 commentaires

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.



2
votes

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


0 commentaires

0
votes

Travailler sur quelque chose de similaire pour un projet personnel et c'est ma mise en œuvre naïve: xxx pré>

Utilisez-le à partir du repli: p> xxx pré>

Fully Way: P>

(map (comp vec drop-last) (keys-in <your-map> []))


0 commentaires

0
votes

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

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.


0 commentaires