Clojure: Como posso aplicar uma função a um subconjunto das entradas em um hash-map?

StackOverflow https://stackoverflow.com/questions/1638854

  •  08-07-2019
  •  | 
  •  

Pergunta

Eu não sou a Clojure e tentando descobrir como fazer isso.

Eu quero criar um novo hash-mapa que para um subconjunto das chaves do hash mapa aplica uma função aos elementos. Qual é a melhor maneira de fazer isso?

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

Este deverá resultar um mapa com algo como

{:hello "WORLD" :try "This" :foo "BAR"}
Foi útil?

Solução

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

Breakdown:

Ela ajuda a olhar para ele de dentro para fora. Em Clojure, mistura-mapas agir como funções; se você chamá-los como uma função com uma chave como argumento, o valor associado a essa chave é retornado. Assim, dada uma única chave, o valor atual para essa chave pode ser obtido através de:

(some-map some-key)

Queremos levar valores antigos, e alterá-las para novos valores chamando alguma função f sobre eles. Assim, dada uma única tecla, o novo valor será:

(f (some-map some-key))

Queremos associar esse novo valor com esta chave em nossa de hash-map "substituindo" o valor antigo. Isto é o que assoc faz:

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

( "Substituir" está em assustar aspas porque não estamos mutação de um único objeto de hash-map, estamos retornando novo, imutável, alterado de hash-map objetos cada vez que nós chamamos assoc Este ainda é rápido e eficiente. em Clojure porque Maconha mapas são estrutura persistente e share quando você assoc-los.)

Precisamos assoc repetidamente novos valores para o nosso mapa, uma tecla de cada vez. Por isso, precisamos de algum tipo de looping construção. O que nós queremos é começar com o nosso hash mapa original e uma única chave, e depois "update" o valor para essa chave. Então tomamos essa nova mistura-mapa ea chave seguinte, e "atualizar" o valor para que a próxima chave. E repetimos isso para cada chave, um de cada vez, e, finalmente, retornar o hash-mapear nós "acumulado". Isto é o que reduce faz.

  • O primeiro argumento para reduce é uma função que recebe dois argumentos: um valor "acumulador", que é o valor que mantemos "atualização" mais e mais; e um único argumento usado em uma iteração para fazer alguns dos acumulando.
  • O segundo argumento para reduce é o valor inicial passado como o primeiro argumento para este fn.
  • O terceiro argumento para reduce é uma coleção de argumentos a ser passado como o segundo argumento para esta fn, um de cada vez.

Assim:

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

fn-to-update-values-in-our-map é apenas a declaração assoc de cima, envolto em uma função anônima:

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

Assim, colocá-lo em reduce:

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

Em Clojure, há um atalho para escrever funções anônimas: #(...) é um fn anônima constituída por um único formulário, no qual %1 é vinculado ao primeiro argumento para a função anônima, %2 para o segundo, etc. Portanto, nossa fn de acima pode ser escrito de forma equivalente como:

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

Isto dá-nos:

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

Outras dicas

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

Exemplo chamada

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

Espera que isso ajuda.

A seguir 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"}

Pode haver uma maneira melhor de fazer isso. Talvez alguém pode vir até com uma frase agradável:)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top