clojure 中地图向量的累积加法

Cummulative addition on a vector of maps in clojure

我有一个看起来像这样的数据集

[{1 "a"} {2 "b"} {3 "c"}]

我想把它改造成像这样的累积图

{1 "a" 3 "b" 6 "c"}

我认为我目前的方法是冗长的。到目前为止我想出了

(reduce 
  (fn [sum item] 
      (assoc sum (+ (reduce + (keys sum)) 
                    (key (first item))) 
                 (val (first item)))) 
   split-map)

但是按键上的加法不正确。有谁知道我该如何改进?

要简洁地解决这个问题有点尴尬。这是一种方法:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(dotest
  (let-spy
    [x1       [{1 "a"} {2 "b"} {3 "c"}]
     nums     (mapv #(first (first %)) x1)
     chars    (mapv #(second (first %)) x1)
     nums-cum (reductions + nums)
     pairs    (mapv vector nums-cum chars)  ; these 2 lines are
     result   (into {} pairs)]              ;   like `zipmap`
    (is= result {1 "a", 3 "b", 6 "c"})))

通过使用我的 favorite template project 我们可以使用 the Tupelo library 中的 let-spy 并查看每一步打印的结果:

-----------------------------------
   Clojure 1.10.3    Java 15.0.2
-----------------------------------

Testing tst.demo.core
x1       => [{1 "a"} {2 "b"} {3 "c"}]
nums     => [1 2 3]
chars    => ["a" "b" "c"]
nums-cum => (1 3 6)
pairs    => [[1 "a"] [3 "b"] [6 "c"]]
result   => {1 "a", 3 "b", 6 "c"}

Ran 2 tests containing 1 assertions.
0 failures, 0 errors.

当它处理所有单元测试时,只需 trim 关闭 -spy 部分即可保留正常的 (let ...) 形式。


一定要看看这个 list of documentation sources,尤其是 Clojure CheatSheet。

这里是一个可能的计算实现,它广泛使用了 Clojure 序列函数:

(defn cumul-pairs [data]
  (zipmap (rest (reductions ((map (comp key first)) +) 0 data))
          (map (comp val first) data)))

(cumul-pairs [{1 "a"} {2 "b"} {3 "c"}])
;; => {1 "a", 3 "b", 6 "c"}

在此代码中,表达式 (rest (reductions ((map (comp first keys)) +) 0 data)) 计算结果映射的键,表达式 (map (comp first vals) data) 计算值。然后我们将它们与 zipmap. The function reductions works just like reduce but returns a sequence of all intermediate results instead of just the last one. The curious looking subexpression ((map (comp first keys)) +) is the reducing function, where we use a mapping transducer to construct a reducing function from the + 减少函数结合起来,该函数将在添加之前映射输入值。

如果你喜欢换能器:

(require '[net.cgrand.xforms :as xf])
(let [data [{1 "a"} {2 "b"} {3 "c"}]]
    (into {} (comp
                 (map first)
                 (xf/multiplex [(map last)
                                (comp (map first) (xf/reductions +) (drop 1))])
                 (partition-all 2)) data))
=> {1 "a", 3 "b", 6 "c"}

还有一个有趣的版本:

(->> data
     (reductions (fn [[sum] m] (update (first m) 0 + sum)) [0])
     rest
     (into {}))

;;=> {1 "a", 3 "b", 6 "c"}

诀窍是缩减函数对更新当前对的键的先前和当前键值对进行操作:

(reductions (fn [[sum] m] (update (first m) 0 + sum)) [0] data)
;;=> ([0] [1 "a"] [3 "b"] [6 "c"])

可能是最简单(最易读)的版本:

(def ml [{1 "a"} {2 "b"} {3 "c"}])
(defn cumsum [l] (reductions + l))

(let [m  (into (sorted-map) ml)]
  (zipmap  (cumsum (keys m)) (vals m)))

;; => {1 "a", 3 "b", 6 "c"}

这个怎么样?

(defn f [v]
      (zipmap (reductions + (mapcat keys v)) (mapcat vals v)))

适用于地图的原始矢量:

(f [{1 "a"} {2 "b"} {3 "c"}])

;; => {1 "a", 3 "b", 6 "c"}

.. 以及不同长度的地图:

(f [{1 "a"} {2 "b"} {3 "c" 4 "d"} {5 "e" 6 "f" 7 "g"}])

;; => {1 "a", 3 "b", 6 "c", 10 "d", 15 "e", 21 "f", 28 "g"}