2012-02-23 23 views
19

Trong Clojure, tôi muốn kết hợp nhiều bản đồ vào một bản đồ duy nhất nơi ánh xạ với cùng một khóa được kết hợp thành một danh sách.Trong clojure, làm thế nào để hợp nhất một số bản đồ kết hợp ánh xạ với cùng một khóa vào một danh sách?

Ví dụ:

{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny} 

sẽ dẫn đến:

{:weather :sunny, :humor (:happy :sad :happy)} 

Tôi nghĩ về:

(merge-with (comp flatten list) data) 

Nhưng nó không phải là hiệu quả vì flatten có O (n) phức tạp.

Sau đó, tôi đã đưa ra:

(defn agg[x y] (if (coll? x) (cons y x) (list y x))) 
(merge-with agg data) 

Nhưng nó không cảm thấy ngữ. Bạn có ý tưởng nào khác không?

+0

Cuối cùng cũng gây ra vấn đề về giá trị bản đồ của bạn bao gồm các bộ sưu tập .... – mikera

Trả lời

12

Một cách tiếp cận sẽ

(defn merge-lists [& maps] 
    (reduce (fn [m1 m2] 
      (reduce (fn [m [k v]] 
         (update-in m [k] (fnil conj []) v)) 
        m1, m2)) 
      {} 
      maps)) 

Đó là một chút xấu xí, nhưng đó là chỉ vì giá trị của bạn chưa được liệt kê. Nó cũng buộc mọi thứ làm danh sách (vì vậy bạn sẽ nhận được :weather [:sunny] thay vì :weather :sunny). Thành thật mà nói, điều này có thể là một triệu lần dễ dàng hơn cho bạn để làm việc với anyway.

Nếu bạn có từng giá trị dưới dạng véc tơ, bạn có thể chỉ cần thực hiện (apply merge-with into maps).

+0

Tôi đã mong đợi rằng '(áp dụng hợp nhất-với (fnil conj []) {} dữ liệu)' sẽ làm việc, nhưng tiếc là nó không. :( – kotarak

+0

Bạn có ý gì khi 'Nếu bạn có mỗi giá trị như một véc tơ'? Vui lòng viết định nghĩa chính xác của 'bản đồ'. – viebel

+1

@YehonathanSharvit Nếu bản đồ đầu vào của bạn là' {: humor (: happy)} {: humor (: buồn)} 'thay vì' {: humor: happy} {: humor: sad} 'thì' merge-with into' sẽ hoạt động trên chúng. –

1

Bạn có thể thử những điều sau đây, tôi nghĩ rằng nó khá hiệu quả

(reduce 
    (fn [m pair] (let [[[k v]] (seq pair)] 
       (assoc m k (cons v (m k))))) 
    {} 
    data) 

=> {:weather (:sunny), :humor (:happy :sad :happy)} 
+0

Điều này chỉ hoạt động nếu mỗi bản đồ có chính xác một cặp k/v, đó không phải là ấn tượng tôi nhận được của mục tiêu (kể từ khi anh ta nói bản đồ). – amalloy

+0

Nếu bạn định làm theo cách này, bạn có thể bỏ qua 'let', bằng cách thay thế' (fn [m [[k v]]] ...) '. – amalloy

+1

@amalloy Tôi đã thử điều đó nhưng tôi không thể làm cho nó hoạt động mà không để cho: "nth không được hỗ trợ trên loại này: PersistentArrayMap". Có thể là một lỗi phá hoại ở đâu đó trong 1.3? – mikera

0

Dưới đây là một giải pháp mà mỗi giá trị được biểu diễn dưới dạng danh sách, ngay cả khi độc thân:

(->> [{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}] 
    (map first) 
    (reduce (fn [m [k v]] (update-in m [k] #(cons v %))) {})) 

=> {:weather (:sunny), :humor (:happy :sad :happy)} 

Nếu bạn không muốn quấn độc thân trong một danh sách sau đó tôi nghĩ rằng giải pháp ban đầu của bạn là tốt. Cách duy nhất để làm cho nó thành ngữ hơn là sử dụng core.match.

(->> [{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}] 
    (apply merge-with #(match %1 
           [& _] (conj %1 %2) 
           :else [%1 %2]))) 

=> {:weather :sunny, :humor [:happy :sad :happy]} 
1

Merge với chức năng này:

(defn acc-list [x y] 
    (let [xs (if (seq? x) x (cons x nil))] 
    (cons y xs))) 
1

gì về việc sử dụng nhóm-by?Nó không trả lại chính xác những gì bạn yêu cầu nhưng nó là rất giống nhau:

user=> (group-by first (concat {:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny :humor :whooot})) 
{:humor [[:humor :happy] [:humor :sad] [:humor :happy] [:humor :whooot]], :weather [[:weather :sunny]]} 

Hoặc với một thay đổi nhỏ để nhóm theo chức năng:

(defn group-by-v2 
[f vf coll] 
    (persistent! 
    (reduce 
    (fn [ret x] 
     (let [k (f x)] 
     (assoc! ret k (conj (get ret k []) (vf x))))) 
    (transient {}) coll))) 

trở thành:

user=> (group-by-v2 key val (concat {:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny :humor :whooot})) 
{:humor [:happy :sad :happy :whooot], :weather [:sunny]} 
Các vấn đề liên quan