2009-10-28 15 views
5

No estoy en Clojure y trato de descubrir cómo hacerlo.Clojure: ¿Cómo aplico una función a un subconjunto de las entradas en un hash-map?

Quiero crear un nuevo hash-map que para un subconjunto de las teclas en el hash-map aplique una función a los elementos. ¿Cuál es la mejor manera de hacer esto?

(let 
    [my-map {:hello "World" :try "This" :foo "bar"}] 
    (println (doToMap my-map [:hello :foo] (fn [k] (.toUpperCase k))) 

Esto debería traducirse, un mapa con algo como

{:hello "WORLD" :try "This" :foo "BAR"} 

Respuesta

24
(defn do-to-map [amap keyseq f] 
    (reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq))

Desglose:

Ayuda a mirarlo de adentro hacia afuera. En Clojure, los hash-maps actúan como funciones; si los llama como una función con una clave como argumento, se devuelve el valor asociado con esa clave. Así que dado una sola tecla, el valor actual de esa tecla se puede obtener a través de:

(some-map some-key) 

queremos tomar los viejos valores, y cambiarlos a nuevos valores llamando a alguna función f en ellos. Así que da una sola tecla, el nuevo valor será:

(f (some-map some-key)) 

Queremos asociar este nuevo valor con esta clave en nuestro mapa hash "reemplazando" el valor antiguo. Esto es lo que hace assoc:

(assoc some-map some-key (f (some-map some-key))) 

("Reemplazar" está en asustar comillas porque no estamos mutando un solo objeto hash-mapa, estamos volviendo nueva, inmutable, alteración de hash-mapa objetos cada vez que llamamos assoc. Esto sigue siendo rápido y eficiente en Clojure porque hash mapas son persistentes y estructura de la cuota cuando assoc ellos.)

nos necesitan repetidamente assoc nuevos valores a nuestro mapa, una tecla a la vez. Entonces necesitamos algún tipo de construcción de bucle. Lo que queremos es comenzar con nuestro hash-map original y una sola clave, y luego "actualizar" el valor de esa clave. Luego tomamos ese nuevo hash-map y la siguiente clave, y "actualizamos" el valor para esa próxima clave. Y repetimos esto para cada clave, una a la vez, y finalmente devolvemos el hash-map que hemos "acumulado". Esto es lo que hace reduce.

  • El primer argumento de reduce es una función que toma dos argumentos: un valor de "acumulador", que es el valor que mantenemos "actualización" una y otra; y un único argumento usado en una iteración para hacer algo de la acumulación.
  • El segundo argumento para reduce es el valor inicial pasado como el primer argumento para este fn.
  • El tercer argumento para reduce es una colección de argumentos para pasar como el segundo argumento a este fn, uno a la vez.

Así:

(reduce fn-to-update-values-in-our-map 
     initial-value-of-our-map 
     collection-of-keys) 

fn-to-update-values-in-our-map es sólo el assoc comunicado desde arriba, envuelto en una función anónima:

(fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key)))) 

Así conectarlo a reduce:

(reduce (fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key)))) 
     amap 
     keyseq) 

En Clojure, hay una abreviatura para escribir funciones anónimas: #(...) es un fn anónimo que consiste en un único formulario, en el que %1 está ligado al primer argumento de la función anónima, %2 al segundo, etc. Así que nuestro fn de arriba se puede escribir equivalente como:

#(assoc %1 %2 (f (%1 %2))) 

Esto nos da:

(reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq) 
+1

¿Te importaría dar una declaración por descripción de la declaración para nosotros nuevos en Clojure? –

+1

Espero que ayude. –

+0

Esa notación # (...) es asombrosa. ¡Gran respuesta! –

1

El siguiente parece funcionar:

(defn doto-map [ks f amap] 
    (into amap 
    (map (fn [[k v]] [k (f v)]) 
     (filter (fn [[k v]] (ks k)) amap)))) 

user=> (doto-map #{:hello :foo} (fn [k] (.toUpperCase k)) {:hello "World" :try "This" :foo "bar"}) 
{:hello "WORLD", :try "This", :foo "BAR"} 

Puede haber una mejor manera de hacer esto. Tal vez alguien puede subir con un bonito de una sola línea :)

4
(defn doto-map [m ks f & args] 
    (reduce #(apply update-in %1 [%2] f args) m ks)) 

Ejemplo llamada

user=> (doto-map {:a 1 :b 2 :c 3} [:a :c] + 2) 
{:a 3, :b 2, :c 5} 

Espero que esto ayude.

Cuestiones relacionadas