我有一个深度嵌套的列表(列表的列表),我想替换列表中的单个任意元素。我该怎么做?(内置的replace可能会替换多个出现,而我只需要替换一个元素。)
我有一个深度嵌套的列表(列表的列表),我想替换列表中的单个任意元素。我该怎么做?(内置的replace可能会替换多个出现,而我只需要替换一个元素。)
(defn replace-in-list [coll n x]
(concat (take n coll) (list x) (nthnext coll (inc n))))
(defn replace-in-sublist [coll ns x]
(if (seq ns)
(let [sublist (nth coll (first ns))]
(replace-in-list coll
(first ns)
(replace-in-sublist sublist (rest ns) x)))
x))
使用方法:
user> (def x '(0 1 2 (0 1 (0 1 2) 3 4 (0 1 2))))
#'user/x
user> (replace-in-sublist x [3 2 0] :foo)
(0 1 2 (0 1 (:foo 1 2) 3 4 (0 1 2)))
user> (replace-in-sublist x [3 2] :foo)
(0 1 2 (0 1 :foo 3 4 (0 1 2)))
user> (replace-in-sublist x [3 5 1] '(:foo :bar))
(0 1 2 (0 1 (0 1 2) 3 4 (0 (:foo :bar) 2)))
如果您给出任何大于子列表长度的n,将会得到IndexOutOfBoundsException
。这也不是尾递归的。它也不是惯用的写法,因为好的Clojure代码避免使用列表来处理所有事情。这很糟糕。在使用此代码之前,我可能会使用可变的Java数组。我想您已经明白了。
在这种情况下,列表比向量更差的原因:
user> (time
(let [x '(0 1 2 (0 1 (0 1 2) 3 4 (0 1 2)))] ;'
(dotimes [_ 1e6] (replace-in-sublist x [3 2 0] :foo))))
"Elapsed time: 5201.110134 msecs"
nil
user> (time
(let [x [0 1 2 [0 1 [0 1 2] 3 4 [0 1 2]]]]
(dotimes [_ 1e6] (assoc-in x [3 2 0] :foo))))
"Elapsed time: 2925.318122 msecs"
nil
assoc-in
,因为它已经存在了。有空的时候看看assoc-in
的实现;由于向量使用索引提供高效和易于随机访问,因此它的实现很简单直接(与列表版本相比)。seq
进行遍历。你可以以类似列表的方式透明地使用向量,那么为什么不使用向量,获得两者最好的优点呢?[]
和{}
,Clojure代码不像其他Lisp那样是一个巨大的括号块。有些人发现这很烦人,我却认为这使阅读变得更容易。(我的编辑器对()
、[]
和{}
使用不同的语法高亮,这更有助于阅读。)seq
,例如通过lazy-seq
(defn randomly-replace [replace-with in-tree]
(loop [loc dz]
(if (zip/end? loc)
(zip/root loc)
(recur
(zip/next
(if (= 0 (get-random-int 10))
(zip/replace loc replace-with)
loc)))))
这些内容适用于任何嵌套的可序列化对象,甚至包括XML文件。
虽然这与你的问题不完全相关,但如果你有向量而非列表:
user=> (update-in [1 [2 3] 4 5] [1 1] inc)
[1 [2 4] 4 5]
user=> (assoc-in [1 [2 3] 4 5] [1 1] 6)
[1 [2 6] 4 5]
如果可能的话,请使用向量而不是列表,以获得更好的访问行为。如果您必须使用来自各种来源的lazy-seq,则当然不是一个好建议...
(defn replace-item
"Returns a list with the n-th item of l replaced by v."
[l n v]
(concat (take n l) (list v) (drop (inc n) l)))
来自普通观众的一个简单建议:
assoc
随意随机调整该向量的元素;这可能会浪费一些性能;但如果这是一个性能敏感的操作,你本来就应该使用向量。