我有这个深度嵌套的列表(列表列表),我想替换列表中的单个任意元素。我怎样才能做到这一点 ? (内置替换可能会替换多次出现,而我只需要替换一个元素。)
答案 0 :(得分:9)
正如其他人已经说过的那样,如果你需要做这种事情,使用列表真的不是一个好主意。随机访问是为什么做的向量。 assoc-in
有效地做到了这一点。使用列表,您无法远离递归到子列表,并将其中的大多数替换为自身的更改版本,一直到顶部。
虽然这个代码效率低,而且很笨拙,但它会这样做。从皮癣中借来:
(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)))
如果您提供的任何IndexOutOfBoundsException
大于子列表的长度,您将获得n
。它也不是尾递归的。它也不是惯用语,因为好的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
的实现;它简单明了(与列表版本相比),这要归功于矢量通过get
提供高效便捷的索引随机访问。
你也不必引用像你必须引用列表的向量。 Clojure中的列表强烈暗示“我在这里调用函数或宏”。
可以通过seq
遍历向量(以及地图,集等)。你可以以类似列表的方式透明地使用向量,那么为什么不使用向量并且两全其美呢?
矢量也在视觉上突出。由于[]
和{}
的广泛使用,Clojure代码不像其他Lisp那么庞大。有些人觉得这很烦人,我觉得它让事情更容易阅读。 (我的编辑器语法高亮显示()
,[]
和{}
,这有助于提供更多帮助。)
有些情况我会使用数据列表:
lazy-seq
答案 1 :(得分:6)
对于简单的情况,递归替换函数将为您提供您需要的更多复杂性。当事情变得复杂一点时,在zipper函数中破解打开的clojure构建时间:“Clojure包含纯函数,泛型树行走和编辑,使用称为拉链的技术(在命名空间中)压缩)。”
改编自http://clojure.org/other_libraries
中的示例(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)))))
这些将适用于嵌套的任何东西(seq'able)甚至xmls
答案 2 :(得分:5)
它有点不回答你的问题,但如果你有向量而不是列表:
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,这当然不是一个建议......
答案 3 :(得分:0)
您可以使用此功能并根据您的需要调整它(嵌套列表):
(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)))
答案 4 :(得分:0)
来自花生画廊的一个简单的建议:
assoc
; 这可能会浪费一些表现;但如果这是一个对性能敏感的操作,那么你首先要使用矢量。