Clojure:链接分组:使用剩余键上的选择键键入

时间:2016-03-23 16:32:04

标签: clojure

我正在尝试用clojure地图理解一个简单的(如在其他语言中)工作流程。

基本上归结为:如何将这些操作链接起来?

  1. group-by:关键地图矢量

  2. select-keys在没有上一个键的剩余地图上

  3. 再次
  4. group-by(0..n次)和选择键

  5. 最后
  6. count个唯一的密钥实例。

  7. 另见我之前的问题: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

3 个答案:

答案 0 :(得分:3)

重复应用分组是错误的工具:它不能很好地组合自己。相反,查看输入映射并将其中的每一个转换为对您有用的格式(使用formap),然后减少它以构建树结构。这是一个简单的实现:

(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,您可以从中获取计数或其他任何内容。