2010-10-14 13 views
20

Tengo un mapa de Clojure que puede contener valores que son nulos y estoy intentando escribir una función para eliminarlos, sin mucho éxito (soy nuevo en esto).¿Eliminar valores nulos de un mapa?

Ej:

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

Esto se traduce en una secuencia de mapas, que no es lo que esperaba de fusión:

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

me gustaría tener:

{:a 1, :b 2} 

Respuesta

47

su para lista por comprensión devuelve una lista de los mapas, por lo que es necesario aplicar esta lista a la función de combinación de argumentos opcionales:

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

solución más concisa filtrando el mapa como una secuencia y conjunción en un mapa:

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

Dont eliminar falsas valores:

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

Usando dissoc para permitir el intercambio de datos persistentes en lugar de crear un mapa completamente nuevo:

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

+1 para el (segundo filtro ...) enfoque. Muy inteligente. –

+6

Desafortunadamente, el enfoque '(segundo filtro ...)' no funciona. '(into {} (filter second {: a true: b false}))' da '{: a true}' - – kotarak

+0

@kotorak: Buena captura en el problema nil/false -> reedit –

5

Puede aplastarlo en un mapa:

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

Se puede utilizar reducir.

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

Si por alguna razón desea mantener el orden (que por lo general no es importante en un mapa), puede utilizar dissoc.

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

Aunque Jürgen del (segundo filtro de registro) enfoque tiene mi voto para niftiest Clojure truco, que pensé en tirar otra manera por ahí, esta vez utilizando select-keys: solución

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

Jürgen Hötzel refinado para fijar el nil/tema falsa versión

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

un poco más corto de la solución @thnetos

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

Esta es la que trabaja en los mapas anidados:

(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

Quiero decir, funciona en mapas regulares, pero también cuando tiene, por ejemplo, {: foo {: bar nil: baz "ba"}} – Eelco

+1

Con un mapa que contiene todos los nils, esto realmente debería devolver 'nil'. Por ejemplo, '(remove-nils {: foo {: bar nil: baz nil}})' debería devolver 'nil', pero en su lugar devuelve' {: foo {}} '. ¿Cómo podemos modificar esta respuesta para abordar el problema de un mapa con * todos * 'valores nulos'? –

1

Aquí hay una que funciona en mapas y vectores:

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

Una variante de @ respuesta 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))) 

Al punto de @ broma0, elides cualquier mapa vacío.

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

Reducir-kv también se puede utilizar para eliminar las claves

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