2009-10-28 15 views
5

Tôi không phải là Clojure và cố gắng tìm ra cách để thực hiện điều này.Clojure: Làm cách nào để áp dụng một hàm cho một tập con của các mục nhập trong bản đồ băm?

Tôi muốn tạo bản đồ băm mới cho tập con của các khóa trong bản đồ băm áp dụng một hàm cho các phần tử. Cách tốt nhất để làm việc này là gì?

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

này sau đó sẽ dẫn đến một bản đồ với một cái gì đó giống như

{:hello "WORLD" :try "This" :foo "BAR"} 

Trả lời

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

Breakdown:

Nó giúp để nhìn vào nó trong ra ngoài. Trong Clojure, Hash-map hoạt động như các hàm; nếu bạn gọi chúng như một hàm với một khóa làm đối số, giá trị được kết hợp với khóa đó sẽ được trả về. Vì vậy, cho một chìa khóa duy nhất, giá trị hiện tại cho khóa đó có thể thu được thông qua:

(some-map some-key) 

Chúng tôi muốn lấy giá trị cũ, và thay đổi chúng để các giá trị mới bằng cách gọi một số chức năng f trên chúng. Vì vậy, với một khóa duy nhất, giá trị mới sẽ là:

(f (some-map some-key)) 

Chúng tôi muốn kết hợp giá trị mới này với khóa này trong bản đồ băm của chúng tôi, "thay thế" giá trị cũ. Đây là những gì assoc làm:

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

("Thay thế" là trong sợ hãi dấu ngoặc kép bởi vì chúng ta không biến đổi một đối tượng băm-bản đồ duy nhất, chúng ta đang trở về mới, không thay đổi, thay đổi băm-map đối tượng mỗi lần chúng ta gọi là assoc. Đây là vẫn nhanh và hiệu quả trong Clojure vì băm-bản đồ là dai dẳng và cơ cấu cổ phần khi bạn assoc họ.)

chúng ta cần phải lặp đi lặp lại assoc giá trị mới vào bản đồ của chúng tôi, một trong những chìa khóa cùng một lúc. Vì vậy, chúng tôi cần một số loại xây dựng looping. Những gì chúng tôi muốn là bắt đầu với bản đồ băm ban đầu của chúng tôi và một khóa duy nhất, sau đó "cập nhật" giá trị cho khóa đó. Sau đó, chúng tôi lấy bản đồ băm mới và khóa tiếp theo và "cập nhật" giá trị cho khóa tiếp theo đó. Và chúng tôi lặp lại điều này cho mỗi khóa, từng lần một và cuối cùng trả về bản đồ băm mà chúng tôi đã "tích luỹ". Đây là những gì reduce làm.

  • Đối số đầu tiên cho reduce là hàm lấy hai giá trị: giá trị "tích lũy", là giá trị chúng tôi giữ "cập nhật" lặp đi lặp lại; và một đối số được sử dụng trong một lần lặp để thực hiện một số tích lũy.
  • Đối số thứ hai cho reduce là giá trị ban đầu được chuyển làm đối số đầu tiên cho số fn này.
  • Đối số thứ ba cho reduce là tập hợp các đối số được chuyển làm đối số thứ hai cho số này fn, mỗi lần một đối số.

Vì vậy:

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

fn-to-update-values-in-our-map chỉ là assoc tuyên bố từ trên cao, được bọc trong một chức năng ẩn danh:

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

Vì vậy, cắm nó vào reduce:

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

Trong Clojure, có một cách viết tắt để viết chức năng ẩn danh: #(...) là một ẩn danh fn bao gồm một biểu mẫu duy nhất, trong đó %1 được ràng buộc với đối số đầu tiên cho hàm ẩn danh, %2 đến thứ hai, v.v. Vì vậy, chúng tôi fn từ trên có thể được viết tương đương như:

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

này cho chúng ta:

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

Bạn có thể đưa ra tuyên bố bằng mô tả tuyên bố cho chúng tôi về Clojure mới không? –

+1

Hy vọng rằng sẽ giúp. –

+0

Ký hiệu # (...) này thật tuyệt vời. Câu trả lời chính xác! –

1

Sau đây dường như làm việc:

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

Có thể có một cách tốt hơn để làm điều này. Có lẽ ai đó có thể đưa ra một đẹp một lót :)

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

gọi Ví dụ

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

Hy vọng điều này sẽ hữu ích.

Các vấn đề liên quan