online - clojure windows
Cómo filtrar un mapa persistente en Clojure? (5)
Gracias por su comentario a Michał Marczyk:
(defn filter* [f map]
(reduce (fn [m [k v :as x]]
(if-not (f x)
(dissoc m k)
m))
map map))
user> (filter* #(-> % val (= 1)) {:a 1 :b 1 :c 2})
{:a 1, :b 1}
No veo que vayas a ganar mucho con esta versión de Michał.
Tengo un mapa persistente que quiero filtrar. Algo como esto:
(filter #(-> % val (= 1)) {:a 1 :b 1 :c 2})
Lo anterior aparece como ([:a 1] [:b 1])
(una secuencia diferida de entradas de mapa). Sin embargo, quiero obtener {:a 1 :b 1}
.
¿Cómo puedo filtrar un mapa para que siga siendo un mapa sin tener que reconstruirlo a partir de una secuencia de entradas de mapas?
Me probé en macro para esto basado en la versión de Kotarak. Es mi primer macro haciendo algo útil, así que tengan paciencia y comentarios bienvenidos.
(defmacro filter-map [bindings pred m]
`(select-keys ~m
(for [~bindings ~m
:when ~pred]
~(first bindings)
)
)
)
Ejemplo
user=> (filter-map [key val] (even? (:attr val)) {:a {:attr 2} :b {:attr 3} :c {:attr 4}})
{:c {:attr 4}, :a {:attr 2}}
Necesita recorrer todas las entradas, pero puede aprovechar los mapas persistentes de Clojures:
(apply dissoc my-map (for [[k v] my-map :when (not= v 1)] k))
Y otro:
(let [m {:a 1 :b 2 :c 1}]
(select-keys m (for [[k v] m :when (= v 1)] k)))
(into {} (filter #(-> % val (= 1)) {:a 1 :b 1 :c 2}))
Por supuesto, esto reconstruye el mapa a partir de una secuencia de entradas de mapas, pero no hay forma de evitarlo. Si vas a filtrar las entradas por valor, vas a tener que ir una por una para ver qué valores coinciden con tu predicado y cuáles no.
Actualizado (ver comentarios a continuación):
Con la nueva función de keep
, cuya fuente se puede ver here (debería funcionar bien en Clojure 1.1 si quieres respaldar), esta parece una buena manera de hacerlo si no usas nil
como clave :
(let [m {:a 1 :b 1 :c 2}]
(apply dissoc m (keep #(-> % val (= 1) (if nil (key %))) m)))
; => {:a 1, :b 1}
Además, si realmente ve una desaceleración relacionada con la reconstrucción de su mapa, puede usar un mapa transitorio en el paso de reconstrucción:
(persistent! (loop [m (transient {})
to-go (seq [[:a 1] [:b 2]])]
(if to-go
(recur (apply assoc! m (first to-go))
(next to-go))
m)))
; => {:a 1, :b 2}