¿Cómo actualizar los valores de mapa x con y en Clojure?

Estoy tratando de hacer una función en Clojure que tome un mapa, valores xey como parámetros y luego pase por todos los elementos en un mapa y reemplace potencialmente todos los valores x (si hay alguno) con y.

(defn Change-x-to-y-in-map [map x y]; code here)

Por ejemplo, si tuviera un mapa como {: a 1: b 2: c 1} y llamaría a la función con parámetros [map 1 "¡funciona!"], La función debería regresar el siguiente mapa: {: a "¡funciona!" : b 2: c "funciona!} .

Así que reemplazaría todas las claves con valor 1 a claves con valor "¡funciona!".

¡Gracias de antemano por tu ayuda!

0
Eche un vistazo a la función fmap , que hace esto: github.com/clojure/algo.generic/blob/…
agregado el autor Josh, fuente
Eso requeriría una búsqueda lineal del mapa, que no es ideal. Dicho esto, esto podría hacerse de forma trivial utilizando map o para . ¿Has intentado usar alguno de esos?
agregado el autor Carcigenicate, fuente

6 Respuestas

La función map-vals ya existe en la biblioteca Tupelo . Aplica una función tx-fn a cada valor en el mapa, creando un nuevo mapa de salida. Las pruebas unitarias lo muestran en acción:

  (let [map-123 {:a 1 :b 2 :c 3}
        tx-fn   {1 101 2 202 3 303}]
    (is= (t/map-vals map-123 inc)   {:a   2, :b   3, :c   4})
    (is= (t/map-vals map-123 tx-fn) {:a 101, :b 202, :c 303}))

En el primer caso, la función inc se aplica a cada valor en el mapa de entrada (IM).

El segundo ejemplo utiliza un "mapa de transformación" como tx-fn , donde cada par [k v] indica la transformación deseada de los valores antiguos a los nuevos, respectivamente, en IM. Por lo tanto, vemos valores en map-123 cambiados como:

1 -> 101
2 -> 202 
3 -> 303

También está disponible una función hermana map-keys :

(dotest
  (let [map-123 {1 :a 2 :b 3 :c}
        tx-fn   {1 101 2 202 3 303}]
    (is= (t/map-keys map-123 inc)   {  2 :a   3 :b   4 :c})
    (is= (t/map-keys map-123 tx-fn) {101 :a 202 :b 303 :c}))
2
agregado

A partir de la respuesta de Stefan , podemos modificar el mapa inicial en lugar de crear uno nuevo:

(defn update-v [m ov nv] 
  (reduce-kv (fn [acc k v] (if (= v ov) (assoc acc k nv) acc))
             m m))

Y podemos usar un transitorio para hacer las modificaciones a:

(defn update-v [m ov nv] 
  (persistent!
    (reduce-kv (fn [acc k v] (if (= v ov) (assoc! acc k nv) acc))
             (transient m) m)))

Estos cambios deberían (hmmmmmm) acelerar las cosas un poco.

(Por la presente, coloco este código bajo la licencia Apache 2.0 si no puede tomarlo bajo el valor predeterminado SO CC-BY SA)

2
agregado
@StefanKamphausen Hecho.
agregado el autor Thumbnail, fuente
Me parece bien. ¿Usted también lo pondría bajo ASL? De lo contrario, OP no podrá usar sus extensiones en un código que no sea CC.
agregado el autor Stefan Kamphausen, fuente

Puede hacer esto genéricamente sobre cualquier formulario con clojure.walk funciones:

(defn replace-vals [m v r]
  (walk/postwalk
    (fn [e] (if (= e v) r e))
    m))

(replace-vals {:a 1 :b 2 :c 1} 1 "Hey!")
=> {:a "Hey!", :b 2, :c "Hey!"}

(replace-vals [1 2 3 4 1] 1 "Hey!")
=> ["Hey!" 2 3 4 "Hey!"]

Esto también funcionará para formularios anidados.

(replace-vals {:a 1 :b {:c 1 :d "Bye!"}} 1 "Hey!")
=> {:a "Hey!", :b {:c "Hey!", :d "Bye!"}}

Si solo desea reemplazar los valores del mapa, puede refactorizar esto:

(defn replace-map-vals [m v r]
  (walk/prewalk
    (fn [e] (if (and (map-entry? e) (= (val e) v))
              [(key e) r]
              e))
    m))

(replace-map-vals {1 "not replaced" :replaced 1} 1 "Hey!")
=> {1 "not replaced", :replaced "Hey!"})

Tenga en cuenta que esta versión utiliza prewalk debido a un postwalk y entradas de mapa .

2
agregado
Interesante. Postwalk solo mira los valores, no las claves?
agregado el autor johnbakers, fuente
Pero (fn [e] (si (= e v) r e)) si e es un par k/v, ¿cómo puede ser = solo el valor v, que excluye la clave?
agregado el autor johnbakers, fuente
En otras palabras, ¿por qué su primer ejemplo de versión en un mapa funciona como sugiere su ejemplo? Tiene la misma funcionalidad que su segunda versión, en los mapas, pero no veo cómo puede ser eso. Ambas versiones están reemplazando valores, no solo la segunda versión.
agregado el autor johnbakers, fuente
@johnbakers, mi ejemplo original analiza todos los valores del formulario, por lo que se incluirían las claves. Se agregó otro ejemplo usando prewalk que solo intenta reemplazar los valores del mapa.
agregado el autor Taylor Wood, fuente
@johnbakers intenta usar prewalk-demo o postwalk-demo en un formulario para ver cómo se realiza. Sí, la función recibirá toda la entrada del mapa en un solo paso, pero también recibirá la clave individual y el valor a medida que se atraviesa.
agregado el autor Taylor Wood, fuente

Una versión fácil de usar utilizaría reduce-kv directamente:

(defn update-v [m ov nv] 
  (reduce-kv (fn [acc k v]
              (assoc acc k (if (= v ov) nv v)))
             {} m))

(update-v {:x 1 :y 2 :z 1} 1 "It Works!") => {:x "It Works!", :y 2, :z "It Works!"}

(Por la presente, coloco este código bajo la licencia Apache 2.0 si no puede tomarlo bajo el valor predeterminado SO CC-BY SA)

2
agregado
Tengo mal el requisito: comentario eliminado.
agregado el autor Thumbnail, fuente
agregado el autor Thumbnail, fuente
Eso es verdad. Sin embargo, no sabría ninguna solución que no itere sobre el mapa.
agregado el autor Stefan Kamphausen, fuente

Espectro de solución para arrancar:

(defn replace-vals [m v r] 
    (setval [MAP-VALS (partial = v)] r m))
0
agregado

Una forma es recorrer las claves y los valores del mapa con para y usar en para construir el nuevo mapa:

(defn val-replace [m x y]
  (into {} (for [[k v] m]
             [k (if (= v x) y v)])))

> (val-replace {:x 1 :y 2 :z 1} 1 "It Works!")
{:x "It Works!", :y 2, :z "It Works!"}

> (val-replace1 {:a "" :b 4 :c ""} "" nil)
{:a nil, :b 4, :c nil}
0
agregado
¡Lo siento! Fijo. Tuvo el x en lugar del v en la cláusula else del if.
agregado el autor jas, fuente
¡Muchas gracias! Ahora funciona :)
agregado el autor Ville Lehtonen, fuente