【问题标题】:Sum values in clojure and add totals to dataset对 clojure 中的值求和并将总计添加到数据集
【发布时间】:2012-06-06 13:46:42
【问题描述】:

我是 clojure 的新手,有以下数据集,我从 xml 文档中解析并显示在 excel 文件中:

({:Total 28, :p3percent 89.28571428571429, :p2percent 0.0, :p1percent 10.71428571428571, :APP "A", :p1 3, :p2 0, :p3 25}
 {:Total 40, :p3percent 92.5, :p2percent 0.0, :p1percent 7.5, :APP "b", :p1 3, :p2 0, :p3 37} 
 {:Total 64, :p3percent 93.75, :p2percent 0.0, :p1percent 6.25, :APP "c", :p1 4, :p2 0, :p3 60} 
 {:Total 128, :p3percent 83.59375, :p2percent 12.5, :p1percent 3.90625, :APP "d", :p1 5, :p2 16, :p3 107}
 {:Total 6, :p3percent 83.33333333333333, :p2percent 16.66666666666667, :p1percent 0.0, :APP "e", :p1 0, :p2 1, :p3 5}
 {:Total 8, :p3percent 87.5, :p2percent 12.5, :p1percent 0.0, :APP "f", :p1 0, :p2 1, :p3 7})

我想对每个键的值求和/平均,并在数据集中创建一个新条目,使用 APP 键“Total”,然后在最后一行显示所有求和/平均值。这可以在 excel 中轻松完成,但我显然想先在 clojure 中完成。

我知道如何获取每个键的总和,即 (apply + map(:p1 dataset)),但是如何创建一个函数来遍历数据集并将总计添加为数据集中的额外行?

谢谢

D

【问题讨论】:

    标签: clojure


    【解决方案1】:

    试试merge-with 函数。检查链接中的第二个示例。
    我想这就是你想要的。此功能将帮助您创建最后一个“总计”行。唯一的问题是您有一个不是数字的字段。因此,您可以将忽略字符串的特殊函数传递给merge-with

    【讨论】:

    • 您好,感谢您的回复,我远离 merge-with + 因为我还想获得某些键的平均值和总和...我真的应该在原始描述中提到这一点,道歉。
    【解决方案2】:

    我想对每个键的值求和/求平均值,并在数据集中创建一个新条目,APP 键为“Total”,然后在最后一行显示所有求和/平均值。

    我相信之前的答案误解了这个问题。如果您想在数据集中创建一个新条目,其中包含某些键的总计和其他键的平均值,那么我的回答可能会有所帮助。


    首先根据数字集合定义sumavg。您以后可以随时改进这些函数的实现,所以暂时保持简单。

    (defn sum [coll] (reduce + coll))
    ;;(sum [1 2 3])
    ;;=> 6
    (defn avg [coll] (/ (sum coll) (count coll)))
    ;;(avg [1 2 3])
    ;;=> 2
    

    为避免重复,请定义一个函数来减少数据集。

    (defn dataset-keys [d] (reduce #(into %1 (keys %2)) #{} d))
    
    (defn reduce-dataset
      [f val dataset]
      (reduce (fn [m k] (assoc m k (f (map k dataset))))
              val
              (dataset-keys dataset)))
    

    reduce-dataset 期望 val 是地图,dataset 是地图集合,就像您的数据集一样。

    使用reduce-datasettotalsaverages 定义为sumavg

    (defn totals [dataset] (reduce-dataset sum {:APP "Totals"} dataset))
    (defn averages [dataset] (reduce-dataset avg {:APP "Averages"} dataset))
    

    由于您想要获取数据集中某些键的总和以及其他键的平均值,因此您需要一种方法来仅选择数据集中的这些键。

    (defn select-cols [dataset ks] (map #(select-keys % ks) dataset))
    

    现在您拥有了在数据集中有选择地计算总数和平均值所需的一切。

    (totals (select-cols your-dataset [:Total :p1 :p2 :p3]))
    ;;{:Total 274, :p2 18, :p3 241, :p1 15, :APP "Totals"}
    
    (averages (select-cols your-dataset [:p1percent :p2percent :p3percent]))
    ;; {:p3percent 88.32713293650794, :p1percent 4.728422619047618, :p2percent 6.9444444444444455, :APP "Averages"}
    

    您可以使用 conj 将结果与原始数据集结合起来。

    (conj dataset
          (totals (select-cols dataset [:Total :p1 :p2 :p3]))
          (averages (select-cols dataset [:p1percent :p2percent :p3percent])))
    

    这会将两行添加到数据集,一用于总计,另一行用于平均值。要添加单行,可以在 conj'ing 之前合并结果。

    (conj dataset
          (merge (totals (select-cols dataset [:Total :p1 :p2 :p3]))
                 (averages (select-cols dataset [:p1percent :p2percent :p3percent]))
                 {:APP "Total/Avg"}))
    

    在键冲突的情况下,merge 将始终使用它看到的最后一个值,因此在上面的示例中,:APP 的值将是 "Total/Avg" 而不是 "Totals""Averages"

    【讨论】:

      【解决方案3】:

      如果您只是询问生成包含对应键的总数或平均值的地图,reduce 可以很好地做到这一点:

      user> (pprint (reduce #(assoc %2 :Total (+ (:Total %2) (:Total %1))) {:Total 0} 条数据)) {:p1 0, :p3 7, :p2% 12.5, :p2 1, :p1% 0.0, :APP "f", :p3% 87.5, :总计 274} 零

      这可以封装在一个函数中,该函数保留重要的原始密钥,这样您就可以取一个密钥的总和,然后也取它的平均值:

      (defn sum-key [key new-key map] 
        (reduce #(assoc %2 new-key (+ (%1 new-key) (%2 key))) {new-key 0} data))
      #'user/sum-key
      user> (sum-key :Total :Total-sum data)
      {:p1 0, :p3 7, :p2percent 12.5, :p2 1, :p1percent 0.0, :APP "f", :p3percent 87.5, :Total-sum 274, :Total 8}
      

      然后可以链接这个函数来总结你想要的键

      user> (->> data 
              (sum-key :Total :Total-sum) 
              (sum-key :p1 :p1-sum) 
              (sum-key :p2 :p2-sum))
      {:p1 0, :p2-sum 18, 
       :p3 7, :p2percent 12.5, 
       :p2 1, :p1percent 0.0, 
       :APP "f", :p3percent 87.5, 
       :Total 8}
      

      如果您想要运行总计,请改用reductions

      【讨论】:

      • @Arthur_Ulfeldt 什么时候使用reduce会导致整个序列出现问题?我之所以这么问,是因为我定期看到 cmets 表明为什么不应该使用 reduce。 tnx
      • reduce 产生的结果是将你给它的所有数据减少为一个值(因此它的名字)所以如果你不希望它实现整个流然后使用 (take X data) 代替. Reduce 不包含对惰性序列头部的引用,因此在大于 ram 的数据集上使用它是安全的。
      • 我编辑以更好地匹配问题,我(我认为)最初被误解了。
      • 听起来您需要保留地图中的原始数据并添加包含平均值、总和等的新键。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-02
      • 2011-12-17
      • 1970-01-01
      • 1970-01-01
      • 2017-04-05
      • 1970-01-01
      相关资源
      最近更新 更多