2010-05-02 19 views
31

Tengo un mapa persistente que quiero filtrar. Algo como esto:Cómo filtrar un mapa persistente en Clojure?

(filter #(-> % val (= 1)) {:a 1 :b 1 :c 2}) 

Lo anterior sale como ([:a 1] [:b 1]) (una secuencia lenta de entradas de mapa). Sin embargo, quiero ser 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 mapa?

Respuesta

45

Y otro:

(let [m {:a 1 :b 2 :c 1}] 
    (select-keys m (for [[k v] m :when (= v 1)] k))) 
17
(into {} (filter #(-> % val (= 1)) {:a 1 :b 1 :c 2})) 

Por supuesto, esto hace reconstruir el mapa de una secuencia de entradas del mapa, pero no hay manera 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 abajo):

Con la función de reciente introducción keep, la fuente de la que se puede ver here (debería funcionar bien en Clojure 1.1 si desea backport), esto parece un buen manera de ir sobre él si no se utiliza como una clave nil:

(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 usted ve realmente una desaceleración relacionada con la reconstrucción de su mapa, puede utilizar un mapa transitoria en la fase 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} 
+0

Bueno, teóricamente * * usted podría tener que filtrar por valor sin la reconstrucción mediante la devolución de un mapa con las teclas dissoc-ed que corresponden a los valores no coinciden. Tenía la esperanza de que haya una forma de hacerlo compatible con el lenguaje. –

+0

Ok, ya veo lo que quieres decir. Agregaré dos maneras de hacerlo en un segundo, pero tenga en cuenta que no es muy probable que vea una gran ganancia en el departamento de rendimiento (a menos que tenga un mapa realmente grande y solo estará dislocando un pequeño número de llaves). –

+0

Hm, en realidad no es tanto "dos formas de hacerlo" como "una forma de hacerlo y una forma de preocuparse menos por la reconstrucción". No es muy probable que deba preocuparse. :-) –

3

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 vas a ganar mucho con esta versión de Michał vs.

3

tiene que pasar por todas las entradas, pero puede aprovechar Clojures mapas persistentes:

(apply dissoc my-map (for [[k v] my-map :when (not= v 1)] k)) 
1

me trataron a mí mismo en la macro para esta 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}} 
Cuestiones relacionadas