[英]Clojure: Chaining group-by :key with select-keys on remaining keys
我正在嘗試用clojure地圖理解一個簡單的(如在其他語言中)工作流程。
它基本上歸結為:如何鏈接這些操作?
group-by
:鍵入地圖矢量
select-keys
剩余的地圖沒有以前的關鍵
再次group-by
(0..n次)和選擇鍵
count
唯一的密鑰實例。
另請參閱我之前的問題: 地圖中的聚合和計數
例:
給出一張地圖矢量
(def DATA [{:a "X", :b "M", :c "K", :d 10}
{:a "Y", :b "M", :c "K", :d 20}
{:a "Y", :b "M", :c "F", :d 30}
{:a "Y", :b "P", :c "G", :d 40}])
進行group-by
(defn get-tree-level-1 [] (group-by :a DATA))
生成按該特定鍵的值分組的映射。
{ X [{:a X, :b M, :c K, :d 10}],
Y [{:a Y, :b M, :c K, :d 20}
{:a Y, :b M, :c F, :d 30}
{:a Y, :b P, :c G, :d 40}]}
到現在為止還挺好。 但是,如果我想從數據中構建一個樹狀結構 ,這意味着選擇剩余的鍵並忽略一些,請選擇:b
和:c
並忽略:d
,這將在下一級產生:
(def DATA2 [{ :X [{:b "M", :c "K"}],
:Y [{:b "M", :c "K"}
{:b "M", :c "F"}
{:b "P", :c "G"}]}])
最后,計算剩余鍵的所有實例(例如,計算Y
-root下的:b
鍵的所有唯一值):
(def DATA3 [{ :X [{:M 1}],
:Y [{:M 2}
{:P 1}])
我嘗試在group-by
后執行select-keys
,但在第一步后結果為空:
(defn get-proc-sums []
(into {}
(map
(fn [ [k vs] ]
[k (select-keys vs [:b :c])])
(group-by :a DATA))))
重復應用分組是錯誤的工具:它不能很好地組合自身。 相反,查看輸入映射並將其中的每一個轉換為對您有用的格式(使用for
或map
),然后減少它以構建樹結構。 這是一個簡單的實現:
(defn hierarchy [keyseq xs]
(reduce (fn [m [ks x]]
(update-in m ks conj x))
{}
(for [x xs]
[(map x keyseq) (apply dissoc x keyseq)])))
user> (hierarchy [:a :b :c] '[{:a "X", :b "M", :c "K", :d 10}
{:a "Y", :b "M", :c "K", :d 20}
{:a "Y", :b "M", :c "F", :d 30}
{:a "Y", :b "P", :c "G", :d 40}])
{"Y" {"P" {"G" ({:d 40})},
"M" {"F" ({:d 30}),
"K" ({:d 20})}},
"X" {"M" {"K" ({:d 10})}}}
這為您提供了所需的分層格式,其中包含僅包含“剩余”鍵的所有地圖的列表。 通過這個,你可以計算它們,區分它們,刪除:d
鍵,或者你想要的任何其他東西,或者通過編寫另一個處理這個地圖的函數,或者通過調整reduce函數中的內容或者上面的for
comprehension。
錯誤是您正在嘗試從值集合中選擇鍵,而您應該為coll中的每個項目執行此操作,例如使用map
:
(defn get-proc-sums []
(into {}
(map
(fn [ [k vs] ]
[k (map #(select-keys % [:b :c]) vs)])
(group-by :a DATA))))
user> (get-proc-sums)
{"X" ({:b "M", :c "K"}),
"Y" ({:b "M", :c "K"} {:b "M", :c "F"} {:b "P", :c "G"})}
你正在做的是:
user> (group-by :a DATA)
{"X" [{:a "X", :b "M", :c "K", :d 10}],
"Y" [{:a "Y", :b "M", :c "K", :d 20}
{:a "Y", :b "M", :c "F", :d 30}
{:a "Y", :b "P", :c "G", :d 40}]}
然后你正在處理每個鍵值對(讓我們采用“Y”對):
user> (let [[k vals] ["Y" ((group-by :a DATA) "Y")]]
[k vals])
["Y" [{:a "Y", :b "M", :c "K", :d 20}
{:a "Y", :b "M", :c "F", :d 30}
{:a "Y", :b "P", :c "G", :d 40}]]
所以你為地圖矢量做了select-keys
:
user> (select-keys [{:a "Y", :b "M", :c "K", :d 20}
{:a "Y", :b "M", :c "F", :d 30}
{:a "Y", :b "P", :c "G", :d 40}]
[:a :b])
{}
這是合乎邏輯的,因為你在向量中沒有這些鍵。
user> (map #(select-keys % [:a :b]) [{:a "Y", :b "M", :c "K", :d 20}
{:a "Y", :b "M", :c "F", :d 30}
{:a "Y", :b "P", :c "G", :d 40}])
({:a "Y", :b "M"} {:a "Y", :b "M"} {:a "Y", :b "P"})
更新:為了完成整個任務,我建議如下:
(defn process-data [data]
(->> data
(group-by :a)
(map (fn [[k vals]] [k (frequencies (map :b vals))]))
(into {})))
user> (process-data DATA)
{"X" {"M" 1}, "Y" {"M" 2, "P" 1}}
在這里,我將只討論您的問題的工作流程方面,以及思考功能設計的一種方式。 我只提出了許多方法,但我認為這種方式是充分慣用的。 如果你正在尋找一個實現, amalloy提供了一個很好的實現。
你提出的問題是一個完美的遞歸用例。 您希望構建一個嵌套結構,其中每個嵌套級別(除了最后一個)只是在前一個分組結果上遵循相同的分組過程。 最后一層嵌套改為執行計數。 而且你事先並不知道將會有多少級別的嵌套。
你丟棄了:c
和:d
,所以你不妨在開始時這樣做 - 這在邏輯上是一個獨特的處理步驟。
讓我們假設你已經編寫了你的函數(稱之為foo
- 我將其寫作作為讀者的練習)。 它可以根據對自身的遞歸調用來構造嵌套結構。
我們來看看你的示例數據集:
(def DATA [{:a "X", :b "M", :c "K", :d 10}
{:a "Y", :b "M", :c "K", :d 20}
{:a "Y", :b "M", :c "F", :d 30}
{:a "Y", :b "P", :c "G", :d 40}])
讓我們忽略:d
,所以我們的預處理集看起來像:
(def filtered-data [{:a "X", :b "M", :c "K"}
{:a "Y", :b "M", :c "K"}
{:a "Y", :b "M", :c "F"}
{:a "Y", :b "P", :c "G"}])
例
這是一個示例“查詢”:
(foo filtered-data
[:a :b :c])
我們希望它吐出一個看起來有點像這樣的嵌套結構:
[{ :X (foo [{:b "M", :c "K"}]
[:b :c]),
:Y (foo [{:b "M", :c "K"}
{:b "M", :c "F"}
{:b "P", :c "G"}]
[:b :c]}])
這反過來相當於:
[{ :X [{:M (foo [{:c "K"}]
[:c])}],
:Y [{:M (foo [{:c "K"}
{:c "F"}]
[:c]),
:P (foo [{:c "G"}]
[:c])}]
]}
這些foo
可以很容易地識別遞歸的結束並切換到計數行為:
[{ :X [{:M [{:K 1}]}],
:Y [{:M [{:F 1}
{:K 1}],
:P [{:G 1}]
}]
]}
就個人而言,如果我正在構建這樣一個結構,我會針對一個沒有“多余”嵌套的對象,比如這個特里 :
{"X" {"M" {"K" 1}},
"Y" {"M" {"F" 1, "K" 1},
"P" {"G" 1}}
但我不知道你的用例以及這些是否真的是多余的。 如果您可能希望使用此數據生成多個統計信息,那么請查看合金化如何構建一個可以從中獲取計數的壓縮結構 ,或其他任何內容。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.