我有这个深度嵌套的列表(列表的列表),我想替换列表中的单个任意元素。我怎样才能做到这一点 ? (内置替换可能会替换许多出现的元素,而我只需要替换一个元素。)
最佳答案
正如其他人已经说过的,如果您需要做这种事情,使用列表确实不是一个好主意。随机访问是向量的用途。 assoc-in
可以有效地做到这一点。对于列表,您无法摆脱递归到子列表并用其自身的更改版本替换其中大部分的情况,一直回到顶部。
这段代码可以做到这一点,尽管效率低下且笨拙。借用dermatthias:
(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
的实现;它简单明了(与列表版本相比),这要归功于向量通过 get
通过索引提供高效且轻松的随机访问。
您也不必像必须引用列表那样引用向量。 Clojure 中的列表强烈暗示“我在此处调用函数或宏”。
向量(以及 map 、集合等)可以通过 seq
进行遍历。您可以以类似列表的方式透明地使用向量,那么为什么不使用向量并两全其美呢?
矢量在视觉上也很突出。由于 []
和 {}
的广泛使用,Clojure 代码比其他 Lisp 少了一大堆括号。有些人觉得这很烦人,我发现它使事情更容易阅读。 (我的编辑器语法高亮 ()
、[]
和 {}
的方式不同,这更有帮助。)
在某些情况下我会使用数据列表:
- 如果我有一个需要从前面增长的有序数据结构,我永远不需要随机访问
- “手动”构建 seq,如通过
lazy-seq
- 编写一个宏,需要将代码作为数据返回
关于Clojure:如何替换嵌套列表中的元素?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1859471/