我正在尝试用clojure地图理解一个简单的(如在其他语言中)工作流程。
基本上归结为:如何将这些操作链接起来?
group-by
:关键地图矢量
select-keys
在没有上一个键的剩余地图上
group-by
(0..n次)和选择键
count
个唯一的密钥实例。
另见我之前的问题:Aggregate and Count in Maps
示例:
给出地图矢量
(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
并忽略{{ 1}},将在下一级产生:
:d
最后,计算其余键的所有实例(例如,计算(def DATA2 [{ :X [{:b "M", :c "K"}],
:Y [{:b "M", :c "K"}
{:b "M", :c "F"}
{:b "P", :c "G"}]}])
- 根下:b
键的所有唯一值):
Y
我尝试在(def DATA3 [{ :X [{:M 1}],
:Y [{:M 2}
{:P 1}])
后执行select-keys
,但在第一步后结果为空:
group-by
答案 0 :(得分:3)
重复应用分组是错误的工具:它不能很好地组合自己。相反,查看输入映射并将其中的每一个转换为对您有用的格式(使用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函数中发生的事情,或{{ 1}}理解,上面。
答案 1 :(得分:2)
错误在于您尝试从值集合中选择键,而您应该为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}}
答案 2 :(得分: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}]
}]
]}
就个人而言,如果我正在构建这样一个结构,我会针对一个没有“多余”嵌套的目标,例如trie:
{"X" {"M" {"K" 1}},
"Y" {"M" {"F" 1, "K" 1},
"P" {"G" 1}}
但我不知道你的用例以及这些是否真的是多余的。如果您可能希望使用此数据生成多个统计信息,请查看amalloy如何创建condensed structure,您可以从中获取计数或其他任何内容。