Clojure'da bir haritanın değerleri üzerindeki bir işlevi eşleme


138

Bir değer haritasını aynı tuşlarla ancak değerlere uygulanan bir işlevle başka bir haritaya dönüştürmek istiyorum. Clojure api'de bunu yapmak için bir işlev olduğunu düşünürdüm, ama bulamadım.

İşte aradığım şeyin örnek bir uygulaması

(defn map-function-on-map-vals [m f]
  (reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) {} m))
(println (map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %)))
{:b TESTING, :a TEST}

map-function-on-map-valsZaten var olup olmadığını bilen var mı? (Muhtemelen çok güzel bir isim ile) yapardı düşünürdüm.

Yanıtlar:


153

Ben senin reduceversiyonunu beğendim . Bence deyimsel. İşte zaten liste kavrayışı kullanan bir sürüm.

(defn foo [m f]
  (into {} (for [[k v] m] [k (f v)])))

1
Bu sürümü seviyorum çünkü tüm fonksiyonları ve kullandığı gibi anlarsanız süper kısa ve açıktır. Ve eğer yapmazsanız, onları öğrenmek için bir bahane!
Runevault

Katılıyorum. Ben içine fonksiyon bilmiyordum, ama burada kullanmak mükemmel mantıklı.
Thomas

Ah adam görmedin mi? Tedavi için buradasın. Her fırsatta bu işlevden cehennemi kötüye kullanıyorum. Çok güçlü ve kullanışlı.
Runevault

3
Sevdim, ama argüman sırasının herhangi bir nedeni var mı (sorunun yanı sıra)? mapNedense [fm] bekliyordum .
nha

2
{} Yerine (boş m) kullanmanızı önemle tavsiye ederim. Yani belirli bir harita türü olmaya devam edecekti.
nickik

96

Şunları kullanabilirsiniz clojure.algo.generic.functor/fmap:

user=> (use '[clojure.algo.generic.functor :only (fmap)])
nil
user=> (fmap inc {:a 1 :b 3 :c 5})
{:a 2, :b 4, :c 6}

Bu cevabın tarihlere bakmak için biraz geç olduğunu biliyorum, ama sanırım yerinde.
edwardsmatt

Bu onu 1.3.0 clojure haline getirdi mi?
AnnanFay

13
generic.function lib ayrı bir kütüphaneye taşındı: org.clojure/algo.generic "0.1.0" örnek şimdi okunmalıdır: (use '[clojure.algo.generic.functor :only (fmap)]) (fmap inc {:a 1 :b 3 :c 5})
Travis Schneeberger

2
fmapClojureScript'te nasıl bulunacağına dair bir fikrin var mı?
sebastibe

37

İşte bir haritayı dönüştürmenin oldukça tipik bir yolu. zipmap bir anahtar listesi ve bir değer listesi alır ve yeni bir Clojure haritası oluşturan "doğru olanı yapar". Bunları mapdeğiştirmek için tuşların çevresini veya her ikisini birden koyabilirsiniz .

(zipmap (keys data) (map #(do-stuff %) (vals data)))

veya işlevinize sarmak için:

(defn map-function-on-map-vals [m f]
    (zipmap (keys m) (map f (vals m))))

1
Bunun için anahtarları tedarik etmem beni rahatsız ediyor, ama ödemek için yüksek bir fiyat değil. Kesinlikle benim orijinal önerimden çok daha güzel görünüyor.
Thomas

1
Anahtarların ve değerlerin karşılık gelen değerleri aynı sırayla döndürdüğünün garantisi var mı? Sıralı haritalar ve karma haritalar için?
Rob Lachlan

4
Rob: evet, anahtarlar ve valler tüm haritalar için aynı sırayı kullanır - haritadaki seq ile aynı sırayı kullanır. Karma, sıralı ve dizi haritalarının tümü değiştirilemediğinden, bu sırada siparişin değişme şansı yoktur.
Chouser

2
Durum böyle görünüyor, ama herhangi bir yerde belgelenmiş mi? En azından anahtarlar ve valler için öğretiler bundan bahsetmiyor. Çalışacağına söz veren resmi belgelere işaret edebilirsem bunu kullanmak daha rahat olur.
Jouni K. Seppänen

1
@Jason Evet, bunu 1.6 sürümündeki belgelere eklediklerini düşünüyorum. dev.clojure.org/jira/browse/CLJ-1302
Jouni K.

23

Clojure Yemek Tarifleri'nden indirgeme-kv vardır:

(defn map-kv [m f]
  (reduce-kv #(assoc %1 %2 (f %3)) {} m))

8

İşte bunu yapmanın oldukça deyimsel bir yolu:

(defn map-function-on-map-vals [m f]
        (apply merge
               (map (fn [[k v]] {k (f v)})
                    m)))

Misal:

user> (map-function-on-map-vals {1 1, 2 2, 3 3} inc))
{3 4, 2 3, 1 2}

2
Açık değilse: anon fonksiyonu destructures anahtarı ve değer k ve v ve daha sonra bir karma-haritası haritalama döndürür k için (fv) .
Siddhartha Reddy

6

map-map, map-map-keysVemap-map-values

Bunun için Clojure'da mevcut bir işlev bilmiyorum, ancak map-map-valueskopyalamakta özgür olduğunuz için bu işlevin bir uygulaması . İki yakından ilişkili fonksiyonları ile geliyor map-mapve map-map-keysayrıca standart kütüphanesinden eksik:

(defn map-map
    "Returns a new map with each key-value pair in `m` transformed by `f`. `f` takes the arguments `[key value]` and should return a value castable to a map entry, such as `{transformed-key transformed-value}`."
    [f m]
    (into (empty m) (map #(apply f %) m)) )

(defn map-map-keys [f m]
    (map-map (fn [key value] {(f key) value}) m) )

(defn map-map-values [f m]
    (map-map (fn [key value] {key (f value)}) m) )

kullanım

Bu şekilde arayabilirsiniz map-map-values:

(map-map-values str {:a 1 :b 2})
;;           => {:a "1", :b "2"}

Ve diğer iki fonksiyon şöyle:

(map-map-keys str {:a 1 :b 2})
;;         => {":a" 1, ":b" 2}
(map-map (fn [k v] {v k}) {:a 1 :b 2})
;;    => {1 :a, 2 :b}

Alternatif uygulamalar

Yalnızca map-map-keysveya map-map-valuesdaha genel map-mapişlev olmadan, aşağıdakilere dayanmayan bu uygulamaları kullanabilirsiniz map-map:

(defn map-map-keys [f m]
    (into (empty m)
        (for [[key value] m]
            {(f key) value} )))

(defn map-map-values [f m]
    (into (empty m)
        (for [[key value] m]
            {key (f value)} )))

Ayrıca, bu ifadeyi tercih ediyorsanız map-map, clojure.walk/walkbunun yerine dayalı alternatif bir uygulama into:

(defn map-map [f m]
    (clojure.walk/walk #(apply f %) identity m) )

Parellel versiyonları - pmap-mapvb.

İhtiyacınız olursa bu işlevlerin paralel sürümleri de vardır. Sadece pmapyerine kullanırlar map.

(defn pmap-map [f m]
    (into (empty m) (pmap #(apply f %) m)) )
(defn pmap-map-keys [f m]
    (pmap-map (fn [key value] {(f key) value}) m) )
(defn pmap-map-values [f m]
    (pmap-map (fn [key value] {key (f value)}) m) )

1
Ayrıca prizmatik harita-vals fonksiyonunu kontrol edin. Geçici geçişler
ClojureMostly

2

Ben bir Clojure n00b, bu yüzden çok daha zarif çözümler olabilir. Benimki burada:

(def example {:a 1 :b 2 :c 3 :d 4})
(def func #(* % %))

(prn example)

(defn remap [m f]
  (apply hash-map (mapcat #(list % (f (% m))) (keys m))))

(prn (remap example func))

Anon fonk, her tuştan ve onun değerinden küçük bir liste yapar. Mapcat, bu işlevi haritanın anahtarları dizisi üzerinde çalıştırır ve tüm işleri tek bir büyük listede birleştirir. "karma haritayı uygula" bu diziden yeni bir harita oluşturur. (% M) biraz garip görünebilir, ilişkili değeri aramak için bir haritaya anahtar uygulamak deyimsel Clojure.

En çok tavsiye edilen okuma: Clojure Cheat Sheet .


Örneğinizde yaptığınız gibi yalak dizilere gitmeyi düşündüm. Ben de seninkinden çok daha fazla fonksiyonun adını sevdim :)
Thomas

Clojure'da, anahtar kelimeler kendilerine aktarılan sırayla kendilerini arayan işlevlerdir. Bu yüzden (: a-map anahtar kelimesi) çalışır. Ancak, anahtar bir anahtar sözcük değilse, kendisini haritaya bakmak için bir işlev olarak kullanmak işe yaramaz. Bu nedenle, anahtarların ne olduğuna bakılmaksızın yukarıdaki (% m) değerini (m%) olarak değiştirmek isteyebilirsiniz.
Siddhartha Reddy

Hata! Bahşiş için teşekkürler, Siddhartha!
Carl Smotricz

2
(defn map-vals
  "Map f over every value of m.
   Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
   f is a function of one arg, which will be called which each value of m, and should return the new value.
   Faster then map-vals-transient on small maps (8 elements and under)"
  [f m]
  (reduce-kv (fn [m k v]
               (assoc m k (f v)))
             {} m))

(defn map-vals-transient
  "Map f over every value of m.
   Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
   f is a function of one arg, which will be called which each value of m, and should return the new value.
   Faster then map-vals on big maps (9 elements or more)"
  [f m]
  (persistent! (reduce-kv (fn [m k v]
                            (assoc! m k (f v)))
                          (transient {}) m)))

1

reduceVersiyonunu beğendim . Çok hafif bir varyasyonla, kayıt yapılarının türünü de koruyabilir:

(defn map-function-on-map-vals [m f]
  (reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) m m))

{}İle değiştirildi m. Bu değişiklikle kayıtlar kayıt olarak kalır:

(defrecord Person [firstname lastname])

(def p (map->Person {}))
(class p) '=> Person

(class (map-function-on-map-vals p
  (fn [v] (str v)))) '=> Person

İle başlayarak {}, kayıt kendi kaybeder recordiness biri kaydederken yetenekleri (örneğin kompakt bellek gösterimi) arzu ederseniz, korumak isteyebilirsiniz.


0

Neden hiç kimsenin hayalet kütüphanesinden bahsetmediğini merak ediyorum . Bu tür bir dönüşümü kodlamayı kolaylaştırmak (ve daha da önemlisi, yazılı kodu anlamak kolay) yapmak için yazılmıştır, ancak yine de çok performanslıdır:

(require '[com.rpl.specter :as specter])

(defn map-vals [m f]
  (specter/transform
   [specter/ALL specter/LAST]
   f m))

(map-vals {:a "test" :b "testing"}
          #(.toUpperCase %))

Saf Clojure'da böyle bir işlev yazmak basittir, ancak farklı veri yapılarından oluşan son derece iç içe koda geçtiğinizde kod çok daha karmaşık hale gelir. Ve burada hayalet giriyor.

Arkadaki motivasyonu ve hayalet ayrıntılarını açıklayan Clojure TV'de bu bölümü izlemenizi tavsiye ederim .


0

Clojure 1.7 ( 30 Haziran 2015'te piyasaya sürüldü ) bunun için şık bir çözüm sunuyor update:

(defn map-function-on-map-vals [m f]
    (map #(update % 1 f) m))

(map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %))
;; => ([:a "TEST"] [:b "TESTING"])
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.