2011-10-23 9 views
5

Hi huys: Tôi muốn ánh xạ "trung bình" cho tất cả các giá trị trong bản đồ. nói rằng tôi có một danh sách các bản đồ:Tôi có thể "chuyển đổi" danh sách bản đồ thành bản đồ danh sách trong Clojure không?

[{"age" 2 "height" 1 "weight" 10}, 
{"age" 4 "height" 4 "weight" 20}, 
{"age" 7 "height" 11 "weight" 40}] 

Và đầu ra mong muốn của tôi là

{"age 5 "height" 5 ....} 

/// Dưới đây là những lời huyên thuyên của bộ não của tôi, tức là cách tôi có thể tưởng tượng làm việc này trong Clojure .. .not phải được thực hiện nghiêm túc quá

transpose danh sách:

{"age" [2 4 7] "height" [1 4 11] } 

và sau đó tôi chỉ có thể làm một cái gì đó như (một lần nữa, chiếm một hàm gọi là freduce đây)

(freduce average (vals (map key-join list))) 

để có được

{"age" 5 "weight" 10 "height" 7}

+1

Bạn sẽ muốn giảm để bạn không phải duyệt qua chuỗi hai lần. Bạn sẽ bắt đầu với một bản đồ trống làm bộ tích lũy của bạn và khi bạn giảm được mỗi bản đồ, tổng hợp tất cả các giá trị với các giá trị tương ứng trong bộ tích lũy. Trên phần tử cuối cùng của danh sách, hãy chia từng giá trị theo độ dài của danh sách. –

+0

Tôi thực sự muốn làm toán học phức tạp hơn trên dữ liệu (độ lệch chuẩn, ...), vì vậy Im nghĩ rằng tôi muốn tách rời cách dữ liệu được sáp nhập từ cách nó được chuyển đổi. – jayunit100

Trả lời

4

Đây là một giải pháp khá dài dòng. Hy vọng rằng ai đó có thể đưa ra một cái gì đó tốt hơn:

(let [maps [{"age" 2 "height" 1 "weight" 10}, 
      {"age" 4 "height" 4 "weight" 20}, 
      {"age" 7 "height" 11 "weight" 40}] 
     ks (keys (first maps)) 
     series-size (count maps) 
     avgs (for [k ks] 
      (/ (reduce + 
         (for [m maps] 
          (get m k))) 
       series-size))] 
    (zipmap ks avgs)) 
+0

Bạn là giải pháp đúng, tôi nghĩ, ở chỗ nó rất dễ đọc. – jayunit100

4

Hãy xem merge-with

Dưới đây là đường đi của tôi tại một số mã thực tế:

(let [maps [{"age" 2 "height" 1 "weight" 10}, 
      {"age" 4 "height" 4 "weight" 20}, 
      {"age" 7 "height" 11 "weight" 40}]] 
    (->> (apply merge-with #(conj %1 %2) 
      (zipmap (apply clojure.set/union (map keys maps)) 
        (repeat [])) ; set the accumulator 
      maps) 
     (map (fn [[k v]] [k (/ (reduce + v) (count v))])) 
     (into {}))) 
+1

Điều này là thanh lịch nhưng có lẽ một chút quá tiên tiến đối với tôi vào thời điểm này. Cảm ơn bạn đã chỉ cho tôi hướng tới hợp nhất - với tôi sẽ tìm hiểu nó ... – jayunit100

6

Tạo bản đồ của vectơ:

 
(reduce (fn [m [k v]] 
      (assoc m k (conj (get m k []) v))) 
     {} 
     (apply concat list-of-maps)) 

Tạo bản đồ của trung bình:

 
(reduce (fn [m [k v]] 
      (assoc m k (/ (reduce + v) (count v)))) 
     {} 
     map-of-vectors) 
+0

Cảm ơn lời giải thích ... có ý nghĩa. – jayunit100

1

Dưới đây là một phiên bản khác sử dụng kết hợp với các không có zipmap.

(let [data [{:a 1 :b 2} {:a 2 :b 4} {:a 4 :b 8}] 
      num-vals (count data)] 
    (->> data (apply merge-with +) 
      (reduce (fn [m [k v]] (assoc m k (/ v num-vals))) {}))) 
+1

Điều này giả định rằng tất cả các khóa đều có mặt trong tất cả các bản đồ, có thể là một giả thiết vô căn cứ (ít nhất là không rõ ràng). – mange

+0

bạn đúng trên giả định khóa, nếu một số bản đồ bị thiếu khóa, bạn sẽ cần phải thay thế '+' trong kết hợp với '# (+ (hoặc% 1 0) (hoặc% 2 0))' – user499049

2
(defn key-join [map-list] 
    (let [keys (keys (first map-list))] 
     (into {} (for [k keys] [k (map #(% k) map-list)])))) 
(defn mapf [f map] 
    (into {} (for [[k v] map ] [k (f v)]))) 
(defn average [col] 
    (let [n (count col) 
     sum (apply + col)] 
     (/ sum n))) 

DEMO

user=> (def data-list [{"age" 2 "height" 1 "weight" 10}, 
{"age" 4 "height" 4 "weight" 20}, 
{"age" 7 "height" 11 "weight" 40}]) 
#'user/data-list 
user=> (key-join data-list) 
{"age" (2 4 7), "height" (1 4 11), "weight" (10 20 40)} 
user=> (mapf average (key-join data-list)) 
{"age" 13/3, "height" 16/3, "weight" 70/3} 
+0

Seems có ý nghĩa. Bạn không chắc chắn nơi áp dụng mức trung bình ... mapf i guess>? – jayunit100

+0

Heartbroken, Thực sự biết? – BLUEPIXY

+0

Viết lại những điều tốt đẹp tùy ý nếu bạn nói rằng nếu tên hàm. – BLUEPIXY

1

Dưới đây là tôi giải pháp một liner:

(def d [{"age" 2 "height" 1 "weight" 10}, 
    {"age" 4 "height" 4 "weight" 20}, 
    {"age" 7 "height" 11 "weight" 40}]) 

(into {} (map (fn [[k v] [k (/ v (count d))]]) (apply merge-with + d))) 

=> {"height" 16/3, "weight" 70/3, "age" 13/3} 

logic như sau:

  • Sử dụng kết hợp với + trên bản đồ để tính tổng cho mỗi giá trị khóa
  • Chia tất cả giá trị kết quả cho tổng số bản đồ để nhận được số trung bình
  • Đặt kết quả trở lại trong băm với (vào {} ...)