2010-10-14 6 views
20

J'ai une carte Clojure qui peut contenir des valeurs nulles et j'essaye d'écrire une fonction pour les supprimer, sans beaucoup de succès (je suis nouveau dans ce domaine).Supprimer les valeurs nulles d'une carte?

Par exemple:

(def record {:a 1 :b 2 :c nil}) 
(merge (for [[k v] record :when (not (nil? v))] {k v})) 

Il en résulte une séquence de cartes, ce qui est pas ce que je pensais de fusion:

({:a 1} {:b 2}) 

Je voudrais avoir:

{:a 1, :b 2} 

Répondre

47

votre pour la compréhension liste retourne une liste de cartes, de sorte que vous devez appliquer cette liste à la fonction de fusion des arguments optionnels:

user> (apply merge (for [[k v] record :when (not (nil? v))] {k v})) 
{:b 2, :a 1}  

solution plus concise en filtrant la carte comme une séquence et conjoindre dans une carte:

user> (into {} (filter second record)) 
{:a 1, :b 2} 

Ne pas supprimer faux valeurs:

user> (into {} (remove (comp nil? second) record)) 
{:a 1, :b false} 

En utilisant dissoc pour permettre aux données persistantes de partage au lieu de créer une nouvelle carte entière:

user> (apply dissoc                        
     record                         
     (for [[k v] record :when (nil? v)] k)) 
{:a 1, :b 2} 
+7

+1 pour l'approche (second filtre ...). Très intelligent. –

+6

Malheureusement, l'approche '(filter second ...)' ne fonctionne pas. '(dans {} (deuxième filtre {: un vrai: b faux}))' donne '{: un vrai}' - – kotarak

+0

@kotorak: Bonne capture sur le problème nil/faux -> reedit –

5

Vous pouvez l'écraser sur une carte:

(into {} (remove (fn [[k v]] (nil? v)) {:a 1 :b 2 :c nil})) 
=> {:a 1 :b 2} 
1

Vous pouvez utiliser réduire.

user> (reduce (fn [m [k v]] (if (nil? v) m (assoc m k v))) {} record) 
{:b 2, :a 1} 

Si pour une raison quelconque, vous voulez garder l'ordre (ce qui est généralement pas important dans une carte), vous pouvez utiliser dissoc.

user> (reduce (fn [m [k v]] (if (nil? v) (dissoc m k) m)) record record) 
{:a 1, :b 2} 
2

approche Bien que Jürgen de (filtre deuxième disque) obtient mon vote pour niftiest Clojure Trick, je pensais que je jette une autre façon là-bas, cette fois en utilisant select-keys:

user> (select-keys record (for [[k v] record :when (not (nil? v))] k)) 
{:b 2, :a 1} 
3

solution Jürgen Hötzel raffinée pour fixer le zéro/faux problème

(into {} (filter #(not (nil? (val %))) {:a true :b false :c nil})) 

Un peu version plus courte de solution @thnetos

(into {} (remove #(nil? (val %)) {:a true :b false :c nil})) 
6

Voici un qui fonctionne sur les cartes imbriquées:

(defn remove-nils 
    [m] 
    (let [f (fn [[k v]] (when v [k v]))] 
    (postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m))) 
+0

Je veux dire, cela fonctionne sur des cartes régulières, mais aussi quand vous avez par exemple. {: foo {: bar nil: baz "ba"}} – Eelco

+1

Avec une carte contenant tous les nils, cela devrait vraiment retourner 'nil'. Par exemple, '(remove-nils {: foo {: bar nil: baz nil}})' devrait retourner 'nil', mais retourne à la place' {: foo {}} '. Comment pouvons-nous modifier cette réponse pour résoudre le problème d'une carte avec des valeurs * all * 'nil'? –

1

est ici qui fonctionne sur les cartes et les vecteurs:

(defn compact 
    [coll] 
    (cond 
    (vector? coll) (into [] (filter (complement nil?) coll)) 
    (map? coll) (into {} (filter (comp not nil? second) coll)))) 
4

Une variante de @ réponse de Eelco:

(defn remove-nils [m] 
    (let [f (fn [x] 
      (if (map? x) 
       (let [kvs (filter (comp not nil? second) x)] 
       (if (empty? kvs) nil (into {} kvs))) 
       x))] 
    (clojure.walk/postwalk f m))) 

Pour le point @ broma0, il élide toutes les cartes vides.

user> (def m {:a nil, :b 1, :c {:z 4, :y 5, :x nil}, :d {:w nil, :v nil}}) 
user> (remove-nils m) 
{:b 1, :c {:z 4, :y 5}} 
user> (remove-nils {}) 
nil 
1

REDUCE-kv peut également être utilisé pour enlever les clés

(reduce-kv (fn [m key value] 
       (if (nil? value) 
        (dissoc m key) 
        m)) 
      {:test nil, :test1 "hello"} 
      {:test nil, :test1 "hello"})