การทำแผนที่ฟังก์ชั่นกับค่าของแผนที่ใน Clojure


138

ฉันต้องการแปลงแผนที่หนึ่งค่าเป็นแผนที่อื่นที่มีปุ่มเหมือนกัน แต่มีฟังก์ชั่นที่ใช้กับค่าต่างๆ ฉันคิดว่ามีฟังก์ชั่นสำหรับทำสิ่งนี้ใน api clojure แต่ฉันหามันไม่เจอ

นี่คือตัวอย่างการใช้งานสิ่งที่ฉันกำลังมองหา

(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-valsมีอยู่แล้ว? ฉันคิดว่ามันทำได้ (อาจมีชื่อที่ดีกว่าด้วย)

คำตอบ:


153

ฉันชอบreduceเวอร์ชั่นของคุณแล้ว ฉันคิดว่ามันเป็นสำนวน นี่คือรุ่นที่ใช้ list comprehension anyways

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

1
ฉันชอบรุ่นนี้เพราะสั้นและชัดเจนมากถ้าคุณเข้าใจฟังก์ชั่นทั้งหมดและมันใช้ และถ้าคุณไม่มีมันก็เป็นข้ออ้างที่จะเรียนรู้พวกเขา!
Runevault

ฉันเห็นด้วย. ฉันไม่รู้ถึงฟังก์ชั่นการใช้งาน แต่มันใช้งานได้ดีที่นี่
โทมัส

โอ้ผู้ชายที่คุณไม่เคยเห็นมาก่อน? คุณอยู่ในการรักษา ฉันใช้นรกจากหน้าที่นั้นทุกโอกาสที่ฉันได้รับ ทรงพลังและมีประโยชน์
Runevault

3
ฉันชอบ แต่มีเหตุผลสำหรับคำสั่งอาร์กิวเมนต์ (นอกเหนือจากคำถาม) หรือไม่ ฉันคาดหวังว่า [fm] เหมือนmapด้วยเหตุผลบางอย่าง
nha

2
ฉันแนะนำอย่างยิ่งให้ใช้ (ว่างเปล่า m) แทน {} ดังนั้นมันจะเป็นแผนที่เฉพาะอย่างต่อเนื่อง
nickik

96

คุณสามารถใช้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}

ฉันรู้ว่าคำตอบนี้ดูช้าไปหน่อย แต่ฉันคิดว่ามันเป็นจุดที่
edwardsmatt

สิ่งนี้ทำให้มันกลายเป็น clojure 1.3.0 หรือไม่?
AnnanFay

13
lib generic.function ได้ย้ายไปที่ห้องสมุดแยก: org.clojure/algo.generic "0.1.0" ตอนนี้ตัวอย่างควรอ่าน:(use '[clojure.algo.generic.functor :only (fmap)]) (fmap inc {:a 1 :b 3 :c 5})
Travis Schneeberger

2
ความคิดใดที่จะทำfmapใน ClojureScript?
sebastibe

37

นี่เป็นวิธีทั่วไปในการแปลงแผนที่ zipmap รับรายการคีย์และรายการค่าและ "ทำสิ่งที่ถูกต้อง" เพื่อสร้างแผนที่ Clojure ใหม่ นอกจากนี้คุณยังสามารถวางmapกุญแจไว้เพื่อเปลี่ยนหรือทั้งสองอย่าง

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

หรือห่อมันในฟังก์ชั่นของคุณ:

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

1
มันทำให้ฉันหงุดหงิดว่าฉันต้องจัดหากุญแจสำหรับมัน แต่มันก็ไม่ได้เป็นราคาที่สูงในการจ่าย แน่นอนยิ่งกว่าคำแนะนำดั้งเดิมของฉันมาก
โทมัส

1
เรารับประกันหรือไม่ว่าคีย์และ vals คืนค่าที่สอดคล้องกันในลำดับเดียวกัน สำหรับแผนที่ที่เรียงลำดับและแผนที่แฮช
Rob Lachlan

4
Rob: ใช่คีย์และ vals จะใช้คำสั่งเดียวกันสำหรับทุกแผนที่ - คำสั่งเดียวกันกับ seq บนแผนที่ใช้ เนื่องจากแฮชที่เรียงลำดับและแผนที่อาเรย์นั้นไม่สามารถเปลี่ยนแปลงได้ดังนั้นจึงไม่มีโอกาสที่จะเปลี่ยนลำดับในเวลาเฉลี่ย
Chouser

2
ดูเหมือนจะเป็นกรณี แต่มีการบันทึกไว้ทุกที่หรือไม่? อย่างน้อยเอกสารสำหรับคีย์และ vals ไม่ได้พูดถึงสิ่งนี้ ฉันจะรู้สึกสะดวกสบายมากขึ้นเมื่อใช้สิ่งนี้ถ้าฉันสามารถชี้ไปที่เอกสารทางการที่สัญญาว่าจะใช้งานได้
Jouni K. Seppänen

1
@Jason Yeah พวกเขาเพิ่มสิ่งนี้ลงในเอกสารประกอบในเวอร์ชัน 1.6 ฉันคิดว่า dev.clojure.org/jira/browse/CLJ-1302
Jouni K. Seppänen


8

นี่เป็นวิธีสำนวนที่ค่อนข้างเป็นธรรมในการทำสิ่งนี้:

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

ตัวอย่าง:

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

2
ถ้ามันจะไม่ชัดเจน: ฟังก์ชั่นอานนท์destructuresสำคัญและคุณค่าให้กับkและโวลต์และจากนั้นส่งกลับกัญชาแผนที่การทำแผนที่kไป(FV)
Siddhartha Reddy

6

map-map, map-map-keysและmap-map-values

ฉันรู้ว่าไม่มีฟังก์ชั่นที่มีอยู่ใน Clojure สำหรับเรื่องนี้ แต่นี่คือการใช้งานฟังก์ชั่นmap-map-valuesนั้นเพราะคุณสามารถคัดลอกได้ฟรี มันมาพร้อมกับฟังก์ชั่นที่เกี่ยวข้องสองอย่างmap-mapและmap-map-keysที่ขาดหายไปจากไลบรารีมาตรฐาน:

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

การใช้

คุณสามารถโทรmap-map-valuesแบบนี้:

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

และอีกสองฟังก์ชั่นเช่นนี้:

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

การใช้งานทางเลือก

หากคุณต้องการเพียงmap-map-keysหรือmap-map-valuesไม่มีmap-mapฟังก์ชั่นทั่วไปมากขึ้นคุณสามารถใช้การใช้งานเหล่านี้ซึ่งไม่พึ่งพา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)} )))

นอกจากนี้ต่อไปนี้เป็นการนำมาใช้ทางเลือกของmap-mapสิ่งนั้นขึ้นอยู่กับclojure.walk/walkแทนintoหากคุณต้องการใช้ถ้อยคำนี้:

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

รุ่นของ Parellel - pmap-mapฯลฯ

นอกจากนี้ยังมีฟังก์ชั่นรุ่นขนานเหล่านี้หากคุณต้องการ พวกเขาเพียงแค่ใช้แทนpmapmap

(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
ตรวจสอบฟังก์ชั่นปริซึมแผนที่ -als มันเร็วกว่าการใช้ชั่วคราวgithub.com/Prismatic/plumbing/blob/…
ClojureMostly

2

ฉันเป็น Clojure n00b ดังนั้นอาจมีทางออกที่สง่างามกว่านี้อีก นี่คือของฉัน:

(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 func สร้าง 2 รายการเล็กน้อยจากแต่ละคีย์และค่า f'ed Mapcat เรียกใช้ฟังก์ชันนี้ตามลำดับของปุ่มบนแผนที่และเชื่อมโยงผลงานทั้งหมดเข้าด้วยกันเป็นรายการใหญ่ "ใช้ hash-map" สร้างแผนที่ใหม่จากลำดับนั้น (% m) อาจดูแปลก ๆ เล็กน้อยมันเป็นสำนวน Clojure สำหรับการใช้คีย์กับแผนที่เพื่อค้นหาค่าที่เกี่ยวข้อง

ส่วนใหญ่อ่านขอแนะนำ: ผู้Clojure โกงแผ่น


ฉันคิดว่าจะเรียงลำดับรางน้ำตามที่คุณทำในตัวอย่างของคุณ ฉันยังชอบชื่อของคุณฟังก์ชั่นกำลังมากขึ้นกว่า :) ของตัวเอง
โทมัส

ใน Clojure คีย์เวิร์ดคือฟังก์ชันที่ค้นหาตัวเองในลำดับใดก็ตามที่ถูกส่งผ่านไปยังพวกเขา นั่นเป็นเหตุผลที่ (: คำหลัก a-map) ทำงาน แต่การใช้คีย์เป็นฟังก์ชันเพื่อค้นหาตัวเองในแผนที่จะไม่ทำงานหากคีย์ไม่ใช่คำหลัก ดังนั้นคุณอาจต้องการเปลี่ยน (% m) ด้านบนเป็น (m%) ซึ่งจะใช้งานได้ไม่ว่าคีย์จะเป็นอะไร
Siddhartha Reddy

อ๊ะ! ขอบคุณสำหรับเคล็ดลับ Siddhartha!
คาร์ล 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

ฉันชอบreduceเวอร์ชั่นของคุณ ด้วยความแตกต่างเล็กน้อยมันยังสามารถรักษาประเภทของโครงสร้างบันทึก:

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

ถูกแทนที่ด้วย{} mด้วยการเปลี่ยนแปลงนั้นระเบียนจะยังคงอยู่:

(defrecord Person [firstname lastname])

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

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

โดยเริ่มต้นด้วย{}บันทึกสูญเสียของrecordinessที่หนึ่งอาจต้องการที่จะรักษาถ้าคุณต้องการความสามารถในการบันทึก (ตัวแทนหน่วยความจำขนาดกะทัดรัดสำหรับอินสแตนซ์)


0

ฉันสงสัยว่าทำไมไม่มีใครพูดถึงห้องสมุดปีศาจ มันถูกเขียนขึ้นเพื่อทำให้การแปลงชนิดนี้ง่ายต่อการเขียนโค้ด (และที่สำคัญยิ่งกว่าคือรหัสที่เขียนง่ายต่อการเข้าใจ) ในขณะที่ยังคงมีประสิทธิภาพมาก:

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

การเขียนฟังก์ชั่นดังกล่าวใน Clojure ล้วนๆนั้นง่าย แต่โค้ดนั้นซับซ้อนกว่ามากเมื่อคุณไปยังโค้ดที่ซ้อนกันสูงซึ่งประกอบด้วยโครงสร้างข้อมูลที่แตกต่างกัน และนี่คือสิ่งที่ปีศาจเข้ามา

ผมขอแนะนำให้ดูตอนนี้บน Clojure ทีวีซึ่งจะอธิบายที่อยู่เบื้องหลังการสร้างแรงจูงใจและรายละเอียดของสถานที่น่ากลัว


0

Clojure 1.7 ( เผยแพร่เมื่อ 30 มิถุนายน 2558) มอบโซลูชันที่หรูหราสำหรับสิ่งนี้ด้วย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"])
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.