Ánh xạ một hàm trên các giá trị của bản đồ trong Clojure


138

Tôi muốn chuyển đổi một bản đồ các giá trị sang một bản đồ khác có cùng khóa nhưng với một hàm được áp dụng cho các giá trị. Tôi sẽ nghĩ rằng có một chức năng để làm điều này trong api clojure, nhưng tôi đã không thể tìm thấy nó.

Đây là một ví dụ thực hiện những gì tôi đang tìm kiếm

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

Có ai biết nếu map-function-on-map-valsđã tồn tại? Tôi sẽ nghĩ rằng nó đã làm (có lẽ với một cái tên đẹp hơn).

Câu trả lời:


153

Tôi thích reducephiên bản của bạn tốt. Tôi nghĩ đó là thành ngữ. Đây là một phiên bản sử dụng danh sách hiểu mọi cách.

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

1
Tôi thích phiên bản này vì nó siêu ngắn và rõ ràng nếu bạn hiểu tất cả các chức năng và nó sử dụng. Và nếu bạn không phải là một cái cớ để tìm hiểu chúng!
Runevault

Tôi đồng ý. Tôi không biết chức năng này, nhưng nó hoàn toàn hợp lý khi sử dụng nó ở đây.
Thomas

Người đàn ông bạn đã không nhìn thấy? Bạn đang ở trong một điều trị. Tôi lạm dụng chức năng đó mỗi khi tôi có cơ hội. Thật mạnh mẽ và hữu ích.
Runevault

3
Tôi thích nó, nhưng có bất kỳ lý do cho thứ tự đối số (ngoài câu hỏi)? Tôi đã mong đợi [fm] như trong mapmột số lý do.
nha

2
Tôi thực sự khuyên bạn nên sử dụng (trống m) thay vì {}. Vì vậy, nó sẽ tiếp tục là một loại bản đồ cụ thể.
nickik

96

Bạn có thể sử dụng 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}

Tôi biết câu trả lời này hơi muộn khi nhìn vào những ngày, nhưng tôi nghĩ đó là điểm.
edwardsmatt

Điều này có làm cho nó thành clojure 1.3.0 không?
AnnanFay

13
lib chung.feft đã chuyển sang một thư viện riêng: org.clojure/algo.generic "0.1.0" ví dụ bây giờ nên đọc: (use '[clojure.algo.generic.functor :only (fmap)]) (fmap inc {:a 1 :b 3 :c 5})
Travis Schneeberger

2
Bất kỳ ý tưởng làm thế nào để tìm thấy fmaptrong ClojureScript?
sebastibe

37

Đây là một cách khá điển hình để biến đổi bản đồ. zipmap lấy một danh sách các khóa và một danh sách các giá trị và "thực hiện đúng" tạo ra một bản đồ Clojure mới. Bạn cũng có thể đặt các mapphím xung quanh để thay đổi chúng hoặc cả hai.

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

hoặc để gói nó trong chức năng của bạn:

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

1
Điều đó làm tôi bực mình vì tôi phải cung cấp chìa khóa cho nó, nhưng nó không phải là một cái giá cao phải trả. Nó chắc chắn trông đẹp hơn rất nhiều so với đề xuất ban đầu của tôi.
Thomas

1
Chúng tôi có đảm bảo rằng các khóa và vals trả về các giá trị tương ứng theo cùng một thứ tự không? Đối với cả bản đồ được sắp xếp và bản đồ băm?
Rob Lachlan

4
Rob: có, các phím và vals sẽ sử dụng cùng một thứ tự cho tất cả các bản đồ - thứ tự giống như một seq trên bản đồ sử dụng. Vì băm, sắp xếp và bản đồ mảng đều không thay đổi, nên không có cơ hội thay đổi thứ tự trong thời gian trung bình.
Chouser

2
Điều đó dường như là trường hợp, nhưng nó được ghi nhận ở bất cứ đâu? Ít nhất là các tài liệu cho các khóa và vals không đề cập đến điều này. Tôi sẽ thoải mái hơn khi sử dụng nó nếu tôi có thể chỉ ra một số tài liệu chính thức hứa hẹn rằng nó sẽ hoạt động.
Jouni K. Seppänen

1
@Jason Vâng, họ đã thêm nó vào tài liệu trong phiên bản 1.6 tôi nghĩ vậy. dev.clojure.org/jira/browse/CLJ-1302
Jouni K. Seppänen

23

Lấy từ Clojure Cookbook, có giảm-kv:

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

8

Đây là một cách khá thành ngữ để làm điều này:

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

Thí dụ:

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

2
Nếu nó không rõ ràng: hàm anon sẽ phá hủy khóa và giá trị thành kv và sau đó trả về ánh xạ băm ánh xạ k đến (fv) .
Siddhartha Reddy

6

map-map, map-map-keysmap-map-values

Tôi biết không có chức năng hiện có trong Clojure cho việc này, nhưng đây là một triển khai của chức năng map-map-valuesđó vì bạn có thể tự do sao chép. Nó đi kèm với hai chức năng liên quan chặt chẽ map-mapmap-map-keys, cũng bị thiếu trong thư viện tiêu chuẩn:

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

Sử dụng

Bạn có thể gọi map-map-valuesnhư thế này:

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

Và hai chức năng khác như thế này:

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

Triển khai thay thế

Nếu bạn chỉ muốn map-map-keyshoặc map-map-values, không có map-mapchức năng tổng quát hơn , bạn có thể sử dụng các triển khai này, không dựa vào 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)} )))

Ngoài ra, đây là một triển khai thay thế map-mapdựa trên clojure.walk/walkthay vì into, nếu bạn thích cụm từ này:

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

Phiên bản Parellel - pmap-map, v.v.

Ngoài ra còn có các phiên bản song song của các chức năng này nếu bạn cần chúng. Họ chỉ đơn giản là sử dụng pmapthay vì 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
Ngoài ra kiểm tra chức năng lăng kính bản đồ. Nó nhanh hơn khi sử dụng tạm thời github.com/Prismatic/plenses/blob/,
ClojureMostly

2

Tôi là một Clojure n00b, vì vậy có thể có nhiều giải pháp thanh lịch hơn nhiều. Đây là của tôi:

(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 tạo một danh sách nhỏ 2 từ mỗi khóa và giá trị f'ed của nó. Mapcat chạy chức năng này qua chuỗi các phím của bản đồ và ghép toàn bộ các tác phẩm thành một danh sách lớn. "Áp dụng bản đồ băm" tạo ra một bản đồ mới từ chuỗi đó. (% M) có thể trông hơi kỳ lạ, đó là Clojure thành ngữ khi áp dụng khóa cho bản đồ để tra cứu giá trị liên quan.

Rất nên đọc: The Clojure Cheat Sheet .


Tôi nghĩ về việc đi theo trình tự máng như bạn đã làm trong ví dụ của bạn. Tôi cũng thích tên của bạn hoạt động nhiều hơn của tôi :)
Thomas

Trong Clojure, từ khóa là các hàm tự tìm kiếm theo bất kỳ chuỗi nào được truyền cho chúng. Đó là lý do tại sao (: từ khóa a-map) hoạt động. Nhưng sử dụng khóa làm chức năng tự tìm kiếm trong bản đồ sẽ không hoạt động nếu khóa không phải là từ khóa. Vì vậy, bạn có thể muốn thay đổi (% m) ở trên thành (m%) sẽ hoạt động bất kể các phím là gì.
Siddhartha Reddy

Giáo sư! Cảm ơn vì tiền boa, 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

tôi thích của bạn reduce phiên bản . Với một biến thể rất nhỏ, nó cũng có thể giữ lại kiểu cấu trúc bản ghi:

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

Các {} được thay thế bởi m. Với sự thay đổi đó, hồ sơ vẫn là hồ sơ:

(defrecord Person [firstname lastname])

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

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

Khi bắt đầu {}, bản ghi sẽ mất tính ghi , mà người ta có thể muốn giữ lại, nếu bạn muốn các khả năng ghi (ví dụ như biểu diễn bộ nhớ nhỏ gọn).


0

Tôi đang tự hỏi tại sao không ai nhắc đến thư viện bóng ma . Nó đã được viết để làm cho loại biến đổi này dễ viết mã (và, thậm chí quan trọng hơn, mã viết dễ hiểu), trong khi vẫn rất hiệu quả:

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

Viết một hàm như vậy trong Clojure thuần túy rất đơn giản, nhưng mã trở nên phức tạp hơn nhiều khi bạn chuyển sang mã được lồng cao bao gồm các cấu trúc dữ liệu khác nhau. Và đây là nơi bóng ma xuất hiện.

Tôi khuyên bạn nên xem tập này trên Clojure TV để giải thích động lực đằng sau và chi tiết về bóng ma .


0

Clojure 1.7 ( phát hành ngày 30 tháng 6 năm 2015) cung cấp một giải pháp thanh lịch cho việc này với 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"])
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.