【问题标题】:Assoc or update Clojure lists and lazy sequences关联或更新 Clojure 列表和惰性序列
【发布时间】:2018-03-22 08:37:48
【问题描述】:

如果我有一个向量(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。 还有其他方法吗?我无法在任何地方找到任何示例。

【问题讨论】:

  • 你真的想做什么?您可以使用地图索引并根据位置更改任意值。那么它仍然是懒惰的。如果你想在惰性序列的中间改变一些值,你首先需要实现序列。要更改位置 42,您首先需要到达那个位置?

标签: list clojure lazy-sequences


【解决方案1】:

编辑或更新惰性序列的单个元素最惯用的方法是什么?

没有用于修改序列/列表的单个元素的内置函数,但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))

或者,如果您想在不进行矢量化的情况下一次性完成这项工作,您也可以使用换能器:

(sequence
  (comp
    (map inc)
    (map-indexed (fn [j x] (if (= 0 j) 666 x))))
  [1 2 3])

实现您的用例是只修改惰性序列中的第一项,然后您可以在保持惰性的同时做一些更简单的事情:

(concat [666] (rest s))

更新回复:关于优化的评论:当更新 1,000,000 个元素的惰性序列中的第 500,000 个元素时,leetwinski 的 assoc-at 函数快了约 8 毫秒,所以如果你想挤出每一点性能,你应该使用他的答案固有的低效操作:

(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))) 快。

【讨论】:

  • 谢谢!这正是我一直在寻找的答案:确认没有内置函数和一些替代实现。例如,我不知道mapv,如果更新是更长的转换链的一部分,那么转换器版本也可能很有用。
  • 注意,如果您有百万个项目的集合,并且您需要修改第一个(或中间项目),您仍然需要在实现时映射整个集合。所以这是完全次优的。
【解决方案2】:

如果确实需要此功能(我对此表示强烈怀疑),我可能会使用类似这样的通用功能:

(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

【讨论】:

  • 你为什么怀疑它是必要的?我有一个代表 CSV 文件的惰性序列,我知道我必须始终“更正”第一行。如果我无法更改输入 CSV,我还剩下什么?
  • 好吧,我想说你在这里的任务降级为仅更改第一项。这是 (cons (do-something (first data)) (rest data)) 。显然还有其他更改非关联集合的项目的情况,但它几乎总是基于它的值而不是索引来更新项目,所以使用简单的map 是合乎逻辑的。
猜你喜欢
  • 1970-01-01
  • 2021-05-25
  • 1970-01-01
  • 2022-09-23
  • 1970-01-01
  • 2011-06-26
  • 2010-12-08
  • 2014-06-17
  • 1970-01-01
相关资源
最近更新 更多