关联或更新 Clojure 列表和惰性序列
Assoc or update Clojure lists and lazy sequences
如果我有一个向量(def v [1 2 3])
,我可以用(assoc v 0 666)
替换第一个元素,得到[666 2 3]
但是如果我在向量映射后尝试做同样的事情:
(def v (map inc [1 2 3]))
(assoc v 0 666)
抛出以下异常:
ClassCastException clojure.lang.LazySeq cannot be cast to clojure.lang.Associative
编辑或更新惰性序列的单个元素最惯用的方法是什么?
我应该使用 map-indexed
并仅更改索引 0 还是将惰性序列实现为向量然后通过 assoc/update 对其进行编辑?
第一个优点是保持懒惰,而第二个效率较低但可能更明显。
我想对于第一个元素我也可以使用 drop 和 cons。
还有其他方法吗?我无法在任何地方找到任何示例。
What's the most idiomatic way of editing or updating a single element of a lazy sequence?
没有 built-in 函数来修改 sequence/list 的单个元素,但 map-indexed
可能是最接近的。这不是列表的有效操作。假设您 不需要 懒惰,我会将序列倒入向量中,这就是 mapv
所做的,即 (into [] (map f coll))
。根据您使用修改后的序列的方式,对其进行矢量化和修改可能同样高效。
您可以使用 map-indexed
编写一个函数来做一些类似和懒惰的事情:
(defn assoc-seq [s i v]
(map-indexed (fn [j x] (if (= i j) v x)) s))
或者如果你想在没有 vector-izing 的情况下懒惰地一次性完成这项工作,你也可以使用传感器:
(sequence
(comp
(map inc)
(map-indexed (fn [j x] (if (= 0 j) 666 x))))
[1 2 3])
意识到你的用例是只修改惰性序列中的第一项,那么你可以在保持惰性的同时做一些更简单的事情:
(concat [666] (rest s))
更新回复:关于优化的评论:leetwinski 的 assoc-at
函数在更新 1,000,000 元素惰性序列中的第 500,000 个元素时快了约 8ms,所以如果你想压缩每个元素,你应该使用他的答案本质上效率低下的操作带来的一点性能:
(def big-lazy (range 1e6))
(crit/bench
(last (assoc-at big-lazy 500000 666)))
Evaluation count : 1080 in 60 samples of 18 calls.
Execution time mean : 51.567317 ms
Execution time std-deviation : 4.947684 ms
Execution time lower quantile : 47.038877 ms ( 2.5%)
Execution time upper quantile : 65.604790 ms (97.5%)
Overhead used : 1.662189 ns
Found 6 outliers in 60 samples (10.0000 %)
low-severe 4 (6.6667 %)
low-mild 2 (3.3333 %)
Variance from outliers : 68.6139 % Variance is severely inflated by outliers
=> nil
(crit/bench
(last (assoc-seq big-lazy 500000 666)))
Evaluation count : 1140 in 60 samples of 19 calls.
Execution time mean : 59.553335 ms
Execution time std-deviation : 4.507430 ms
Execution time lower quantile : 54.450115 ms ( 2.5%)
Execution time upper quantile : 69.288104 ms (97.5%)
Overhead used : 1.662189 ns
Found 4 outliers in 60 samples (6.6667 %)
low-severe 4 (6.6667 %)
Variance from outliers : 56.7865 % Variance is severely inflated by outliers
=> nil
assoc-at
版本在更新大型惰性序列中的 first 项时快 2-3 倍,但并不比 (last (concat [666] (rest big-lazy)))
快。
如果真的需要这个功能(我对此深表怀疑),我可能会选择像这样通用的东西:
(defn assoc-at [data i item]
(if (associative? data)
(assoc data i item)
(if-not (neg? i)
(letfn [(assoc-lazy [i data]
(cond (zero? i) (cons item (rest data))
(empty? data) data
:else (lazy-seq (cons (first data)
(assoc-lazy (dec i) (rest data))))))]
(assoc-lazy i data))
data)))
user> (assoc-at {:a 10} :b 20)
;; {:a 10, :b 20}
user> (assoc-at [1 2 3 4] 3 101)
;; [1 2 3 101]
user> (assoc-at (map inc [1 2 3 4]) 2 123)
;; (2 3 123 5)
另一种方法是使用 split-at
:
(defn assoc-at [data i item]
(if (neg? i)
data
(let [[l r] (split-at i data)]
(if (seq r)
(concat l [item] (rest r))
data))))
请注意,这两个函数都会使 coll 遍历短路,而映射方法不会。这里有一些快速而肮脏的基准:
(defn massoc-at [data i item]
(if (neg? i)
data
(map-indexed (fn [j x] (if (== i j) item x)) data)))
(time (last (assoc-at (range 10000000) 0 1000)))
;;=> "Elapsed time: 747.921032 msecs"
9999999
(time (last (massoc-at (range 10000000) 0 1000)))
;;=> "Elapsed time: 1525.446511 msecs"
9999999
如果我有一个向量(def v [1 2 3])
,我可以用(assoc v 0 666)
替换第一个元素,得到[666 2 3]
但是如果我在向量映射后尝试做同样的事情:
(def v (map inc [1 2 3]))
(assoc v 0 666)
抛出以下异常:
ClassCastException clojure.lang.LazySeq cannot be cast to clojure.lang.Associative
编辑或更新惰性序列的单个元素最惯用的方法是什么?
我应该使用 map-indexed
并仅更改索引 0 还是将惰性序列实现为向量然后通过 assoc/update 对其进行编辑?
第一个优点是保持懒惰,而第二个效率较低但可能更明显。
我想对于第一个元素我也可以使用 drop 和 cons。 还有其他方法吗?我无法在任何地方找到任何示例。
What's the most idiomatic way of editing or updating a single element of a lazy sequence?
没有 built-in 函数来修改 sequence/list 的单个元素,但 map-indexed
可能是最接近的。这不是列表的有效操作。假设您 不需要 懒惰,我会将序列倒入向量中,这就是 mapv
所做的,即 (into [] (map f coll))
。根据您使用修改后的序列的方式,对其进行矢量化和修改可能同样高效。
您可以使用 map-indexed
编写一个函数来做一些类似和懒惰的事情:
(defn assoc-seq [s i v]
(map-indexed (fn [j x] (if (= i j) v x)) s))
或者如果你想在没有 vector-izing 的情况下懒惰地一次性完成这项工作,你也可以使用传感器:
(sequence
(comp
(map inc)
(map-indexed (fn [j x] (if (= 0 j) 666 x))))
[1 2 3])
意识到你的用例是只修改惰性序列中的第一项,那么你可以在保持惰性的同时做一些更简单的事情:
(concat [666] (rest s))
更新回复:关于优化的评论:leetwinski 的 assoc-at
函数在更新 1,000,000 元素惰性序列中的第 500,000 个元素时快了约 8ms,所以如果你想压缩每个元素,你应该使用他的答案本质上效率低下的操作带来的一点性能:
(def big-lazy (range 1e6))
(crit/bench
(last (assoc-at big-lazy 500000 666)))
Evaluation count : 1080 in 60 samples of 18 calls.
Execution time mean : 51.567317 ms
Execution time std-deviation : 4.947684 ms
Execution time lower quantile : 47.038877 ms ( 2.5%)
Execution time upper quantile : 65.604790 ms (97.5%)
Overhead used : 1.662189 ns
Found 6 outliers in 60 samples (10.0000 %)
low-severe 4 (6.6667 %)
low-mild 2 (3.3333 %)
Variance from outliers : 68.6139 % Variance is severely inflated by outliers
=> nil
(crit/bench
(last (assoc-seq big-lazy 500000 666)))
Evaluation count : 1140 in 60 samples of 19 calls.
Execution time mean : 59.553335 ms
Execution time std-deviation : 4.507430 ms
Execution time lower quantile : 54.450115 ms ( 2.5%)
Execution time upper quantile : 69.288104 ms (97.5%)
Overhead used : 1.662189 ns
Found 4 outliers in 60 samples (6.6667 %)
low-severe 4 (6.6667 %)
Variance from outliers : 56.7865 % Variance is severely inflated by outliers
=> nil
assoc-at
版本在更新大型惰性序列中的 first 项时快 2-3 倍,但并不比 (last (concat [666] (rest big-lazy)))
快。
如果真的需要这个功能(我对此深表怀疑),我可能会选择像这样通用的东西:
(defn assoc-at [data i item]
(if (associative? data)
(assoc data i item)
(if-not (neg? i)
(letfn [(assoc-lazy [i data]
(cond (zero? i) (cons item (rest data))
(empty? data) data
:else (lazy-seq (cons (first data)
(assoc-lazy (dec i) (rest data))))))]
(assoc-lazy i data))
data)))
user> (assoc-at {:a 10} :b 20)
;; {:a 10, :b 20}
user> (assoc-at [1 2 3 4] 3 101)
;; [1 2 3 101]
user> (assoc-at (map inc [1 2 3 4]) 2 123)
;; (2 3 123 5)
另一种方法是使用 split-at
:
(defn assoc-at [data i item]
(if (neg? i)
data
(let [[l r] (split-at i data)]
(if (seq r)
(concat l [item] (rest r))
data))))
请注意,这两个函数都会使 coll 遍历短路,而映射方法不会。这里有一些快速而肮脏的基准:
(defn massoc-at [data i item]
(if (neg? i)
data
(map-indexed (fn [j x] (if (== i j) item x)) data)))
(time (last (assoc-at (range 10000000) 0 1000)))
;;=> "Elapsed time: 747.921032 msecs"
9999999
(time (last (massoc-at (range 10000000) 0 1000)))
;;=> "Elapsed time: 1525.446511 msecs"
9999999