Clojure: come posso applicare una funzione a un sottoinsieme delle voci in una mappa hash?

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

  •  08-07-2019
  •  | 
  •  

Domanda

Non sto per Clojure e sto tentando di capire come farlo.

Voglio creare una nuova mappa hash che per un sottoinsieme delle chiavi nella mappa hash applica una funzione agli elementi. Qual è il modo migliore per farlo?

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

Questo dovrebbe quindi risultare in una mappa con qualcosa come

{:hello "WORLD" :try "This" :foo "BAR"}
È stato utile?

Soluzione

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

Breakdown:

Aiuta a guardarlo dentro e fuori. In Clojure, le mappe hash si comportano come funzioni; se li chiamate come una funzione con una chiave come argomento, viene restituito il valore associato a quella chiave. Quindi, data una singola chiave, il valore corrente per quella chiave può essere ottenuto tramite:

(some-map some-key)

Vogliamo prendere vecchi valori e cambiarli in nuovi valori chiamando una funzione f su di essi. Quindi, data una sola chiave, il nuovo valore sarà:

(f (some-map some-key))

Vogliamo associare questo nuovo valore a questa chiave nella nostra mappa hash, " sostituzione " il vecchio valore. Questo è ciò che assoc fa:

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

(" Sostituisci " è tra virgolette perché non stiamo mutando un singolo oggetto hash-map; restituiamo oggetti hash-map nuovi, immutabili, alterati ogni volta che chiamiamo assoc . Questo è ancora veloce ed efficiente in Clojure perché le mappe hash sono persistenti e condividono la struttura quando le le associ .

Dobbiamo ripetutamente assoc nuovi valori sulla nostra mappa, una chiave alla volta. Quindi abbiamo bisogno di una sorta di costrutto loop. Ciò che vogliamo è iniziare con la nostra hash-map originale e una sola chiave, quindi "aggiorna". il valore per quella chiave. Quindi prendiamo quella nuova mappa hash e la chiave successiva e " aggiorna " il valore per quella chiave successiva. E lo ripetiamo per ogni tasto, uno alla volta, e infine restituiamo la mappa hash che abbiamo "accumulato". Questo è ciò che fa riduci .

  • Il primo argomento per ridurre è una funzione che accetta due argomenti: un " accumulatore " valore, che è il valore che manteniamo " aggiornamento " ancora ed ancora; e un singolo argomento usato in una iterazione per fare un po 'di accumulo.
  • Il secondo argomento da ridurre è il valore iniziale passato come primo argomento a questo fn .
  • Il terzo argomento da ridurre è una raccolta di argomenti da passare come secondo argomento a questo fn , uno alla volta.

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

fn-to-update-valori-in-our-map è solo l'istruzione assoc dall'alto, racchiusa in una funzione anonima:

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

Quindi collegandolo a riduci :

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

In Clojure, esiste una scorciatoia per scrivere funzioni anonime: # (...) è un fn anonimo costituito da un singolo modulo, in cui % 1 è associato al primo argomento alla funzione anonima, % 2 al secondo, ecc. Quindi il nostro fn può essere scritto in modo equivalente come:

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

Questo ci dà:

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

Altri suggerimenti

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

Esempio di chiamata

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

Spero che questo aiuti.

Sembra funzionare quanto segue:

(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"}

Potrebbe esserci un modo migliore per farlo. Forse qualcuno può inventare un bel one-liner :)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top