clojure - online - ¿Eliminar valores nulos de un mapa?
clojure windows (9)
Tengo un mapa de Clojure que puede contener valores que son nulos y estoy tratando de escribir una función para eliminarlos, sin mucho éxito (soy nuevo en esto).
P.ej:
(def record {:a 1 :b 2 :c nil})
(merge (for [[k v] record :when (not (nil? v))] {k v}))
Esto da como resultado una secuencia de mapas, que no es lo que esperaba de la fusión:
({:a 1} {:b 2})
Me gustaría tener:
{:a 1, :b 2}
Aquí hay uno que funciona en 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)))
Aquí hay uno 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))))
Aunque el enfoque de Jürgen (filtrar segundo registro) obtiene mi voto por Niftiest Clojure Trick, pensé que iba a tirar otra manera, esta vez usando select-keys
:
user> (select-keys record (for [[k v] record :when (not (nil? v))] k))
{:b 2, :a 1}
Podrías aplastarlo en un mapa:
(into {} (remove (fn [[k v]] (nil? v)) {:a 1 :b 2 :c nil}))
=> {:a 1 :b 2}
Puedes usar reducir.
user> (reduce (fn [m [k v]] (if (nil? v) m (assoc m k v))) {} record)
{:b 2, :a 1}
Si, por algún motivo, desea mantener el pedido (lo que generalmente no es importante en un mapa), puede usar dissoc
.
user> (reduce (fn [m [k v]] (if (nil? v) (dissoc m k) m)) record record)
{:a 1, :b 2}
Solución de Jürgen Hötzel refinada para solucionar el problema nulo / falso
(into {} (filter #(not (nil? (val %))) {:a true :b false :c nil}))
Una versión un poco más corta de la solución @thnetos.
(into {} (remove #(nil? (val %)) {:a true :b false :c nil}))
Su comprensión de la lista devuelve una LISTA de mapas, por lo que debe APLICAR esta lista a la función de combinación como argumentos opcionales:
user> (apply merge (for [[k v] record :when (not (nil? v))] {k v}))
{:b 2, :a 1}
Solución más concisa al filtrar el mapa como una secuencia y unirlo en un mapa:
user> (into {} (filter second record))
{:a 1, :b 2}
No elimine los valores falsos :
user> (into {} (remove (comp nil? second) record))
{:a 1, :b false}
Usar dissoc para permitir el intercambio de datos persistente en lugar de crear un mapa completamente nuevo:
user> (apply dissoc
record
(for [[k v] record :when (nil? v)] k))
{:a 1, :b 2}
Una variación en la 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)))
Para el punto de @ broma0, elude 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
reduce-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"})