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"}
我有一个看起来像这样的数据集
[{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"}