Memetakan fungsi pada nilai-nilai peta di Clojure

138

Saya ingin mengubah satu peta nilai ke peta lain dengan tombol yang sama tetapi dengan fungsi yang diterapkan pada nilai-nilai. Saya akan berpikir ada fungsi untuk melakukan ini di api clojure, tetapi saya tidak dapat menemukannya.

Berikut ini contoh implementasi dari apa yang saya cari

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

Adakah yang tahu kalau map-function-on-map-valssudah ada? Saya akan berpikir itu (mungkin dengan nama yang lebih bagus juga).

Thomas
sumber

Jawaban:

153

Saya suka reduceversi Anda baik-baik saja. Saya pikir ini idiom. Ini versi menggunakan pemahaman daftar.

(defn foo [m f]
  (into {} (for [[k v] m] [k (f v)])))
Brian Carper
sumber
1
Saya suka versi ini karena sangat pendek dan jelas jika Anda memahami semua fungsi dan menggunakannya. Dan jika tidak, itu alasan untuk mempelajarinya!
Runevault
Saya setuju. Saya tidak tahu fungsi ke dalam, tetapi masuk akal menggunakannya di sini.
Thomas
Oh man, kamu belum melihat? Anda berada dalam untuk mengobati. Saya menyalahgunakan fungsi itu setiap kesempatan yang saya dapatkan. Sangat kuat dan bermanfaat.
Runevault
3
Saya suka, tetapi apakah ada alasan untuk urutan argumen (selain pertanyaan)? Saya mengharapkan [fm] seperti mapuntuk beberapa alasan.
nha
2
Saya sangat merekomendasikan untuk menggunakan (kosong m) daripada {}. Jadi itu akan tetap menjadi tipe peta tertentu.
Nickik
96

Anda dapat menggunakan 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}
Arthur Edelstein
sumber
Saya tahu jawaban ini agak terlambat melihat tanggal, tapi saya rasa itu tepat.
edwardsmatt
Apakah ini membuatnya menjadi clojure 1.3.0?
AnnanFay
13
lib generic.function telah pindah ke perpustakaan terpisah: org.clojure/algo.generic "0.1.0" contohnya sekarang harus membaca: (use '[clojure.algo.generic.functor :only (fmap)]) (fmap inc {:a 1 :b 3 :c 5})
Travis Schneeberger
2
fmapAdakah yang bisa Anda temukan di ClojureScript?
sebastibe
37

Berikut adalah cara yang cukup umum untuk mengubah peta. zipmap mengambil daftar kunci dan daftar nilai dan "melakukan hal yang benar" menghasilkan peta Clojure baru. Anda juga bisa meletakkan tombol di mapsekeliling untuk mengubahnya, atau keduanya.

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

atau untuk membungkusnya dengan fungsi Anda:

(defn map-function-on-map-vals [m f]
    (zipmap (keys m) (map f (vals m))))
Arthur Ulfeldt
sumber
1
Ini membuat saya jengkel karena saya harus menyediakan kunci untuk itu, tetapi itu bukan harga tinggi untuk membayar. Jelas terlihat jauh lebih bagus daripada saran asli saya.
Thomas
1
Apakah kami dijamin kunci dan vokal mengembalikan nilai yang sesuai dalam urutan yang sama? Untuk kedua peta yang diurutkan dan peta hash?
Rob Lachlan
4
Rob: ya, kunci dan vals akan menggunakan urutan yang sama untuk semua peta - urutan yang sama dengan seq pada peta. Karena peta hash, diurutkan, dan array semuanya tidak dapat diubah, tidak ada kemungkinan perubahan urutan waktu rata-rata.
Chouser
2
Tampaknya memang demikian, tetapi apakah ini didokumentasikan di mana saja? Setidaknya dokumen untuk kunci dan vokal gagal menyebutkan ini. Saya akan lebih nyaman menggunakan ini jika saya bisa menunjukkan beberapa dokumentasi resmi yang menjanjikan itu akan berhasil.
Jouni K. Seppänen
1
@ Alasan Ya, mereka menambahkan ini ke dokumentasi di versi 1.6 saya pikir. dev.clojure.org/jira/browse/CLJ-1302
Jouni K. Seppänen
23

Diambil dari Clojure Cookbook, ada pengurangan-kv:

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

Berikut cara yang cukup idiomatis untuk melakukan ini:

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

Contoh:

user> (map-function-on-map-vals {1 1, 2 2, 3 3} inc))
{3 4, 2 3, 1 2}
Siddhartha Reddy
sumber
2
Jika tidak jelas: fungsi anon destructures kunci dan nilai k dan v dan kemudian mengembalikan hash-peta pemetaan k ke (FV) .
Siddhartha Reddy
6

map-map,, map-map-keysdanmap-map-values

Saya tahu tidak ada fungsi yang ada di Clojure untuk ini, tapi ini adalah implementasi dari fungsi itu karena map-map-valuesAnda bebas untuk menyalin. Itu datang dengan dua fungsi terkait erat, map-mapdan map-map-keys, yang juga hilang dari perpustakaan standar:

(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) )

Pemakaian

Anda dapat memanggil map-map-valuesseperti ini:

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

Dan dua fungsi lainnya seperti ini:

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

Implementasi alternatif

Jika Anda hanya ingin map-map-keysatau map-map-values, tanpa fungsi yang lebih umum map-map, Anda dapat menggunakan implementasi ini, yang tidak bergantung pada 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)} )))

Juga, inilah implementasi alternatif map-mapyang didasarkan pada clojure.walk/walkalih-alih into, jika Anda lebih suka ungkapan ini:

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

Versi parellel - pmap-map, dll.

Ada juga versi paralel dari fungsi-fungsi ini jika Anda membutuhkannya. Mereka hanya menggunakan pmapbukan 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) )
Rory O'Kane
sumber
1
Juga periksa fungsi prismatik map-vals. Lebih cepat menggunakan transien github.com/Prismatic/plumbing/blob/…
ClojureMostly
2

Saya seorang Clojure n00b, jadi mungkin ada solusi yang jauh lebih elegan. Ini milik saya:

(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))

Fungsi anon membuat 2-daftar kecil dari setiap tombol dan nilainya. Mapcat menjalankan fungsi ini di atas urutan tombol peta dan menggabungkan seluruh pekerjaan menjadi satu daftar besar. "apply hash-map" membuat peta baru dari urutan itu. (% M) mungkin terlihat sedikit aneh, itu adalah Clojure idiomatik karena menerapkan kunci ke peta untuk mencari nilai yang terkait.

Bacaan yang paling sangat direkomendasikan: The Cheat Sheet Clojure .

Carl Smotricz
sumber
Saya berpikir tentang melakukan urutan seperti yang telah Anda lakukan dalam contoh Anda. Saya juga seperti nama Anda fungsi sedang lebih dari :) saya sendiri
Thomas
Di Clojure, kata kunci adalah fungsi yang mencari sendiri dalam urutan apa pun yang diteruskan kepada mereka. Itu sebabnya (: kata kunci a-peta) berfungsi. Tetapi menggunakan kunci sebagai fungsi untuk mencari dirinya sendiri di peta tidak berfungsi jika kuncinya bukan kata kunci. Jadi, Anda mungkin ingin mengubah (% m) di atas menjadi (m%) yang akan berfungsi apa pun kuncinya.
Siddhartha Reddy
Ups! Terima kasih atas tipnya, 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)))
Didier A.
sumber
1

Saya suka reduceversimu. Dengan variasi yang sangat sedikit, ini juga dapat mempertahankan jenis struktur rekaman:

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

The {}digantikan oleh m. Dengan perubahan itu, catatan tetap menjadi catatan:

(defrecord Person [firstname lastname])

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

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

Dengan memulainya {}, record kehilangan recordiness -nya , yang mungkin ingin dipertahankan, jika Anda menginginkan kemampuan record (misalnya representasi memori kompak).

olange
sumber
0

Saya bertanya-tanya mengapa belum ada yang menyebutkan perpustakaan momok . Telah ditulis untuk membuat transformasi semacam ini mudah dikodekan (dan, yang lebih penting, kode tertulis itu mudah dipahami), sambil tetap sangat performan:

(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 %))

Menulis fungsi seperti itu di Clojure murni itu sederhana, tetapi kode menjadi jauh lebih rumit setelah Anda beralih ke kode yang sangat bersarang terdiri dari struktur data yang berbeda. Dan di sinilah momok masuk.

Saya merekomendasikan menonton episode ini di Clojure TV yang menjelaskan motivasi dan detail momok .

mzuther
sumber
0

Clojure 1.7 ( dirilis 30 Juni 2015) memberikan solusi elegan untuk ini dengan 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"])
Jack Pendleton
sumber