Clojure:如何替换嵌套列表中的元素?

时间:2009-12-07 11:38:24

标签: clojure

我有这个深度嵌套的列表(列表列表),我想替换列表中的单个任意元素。我怎样才能做到这一点 ? (内置替换可能会替换多次出现,而我只需要替换一个元素。)

5 个答案:

答案 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那么庞大。有些人觉得这很烦人,我觉得它让事情更容易阅读。 (我的编辑器语法高亮显示()[]{},这有助于提供更多帮助。)

有些情况我会使用数据列表:

  1. 如果我有一个需要从前面发展的有序数据结构,我将永远不需要随机访问
  2. 通过“lazy-seq
  3. ”手动“构建seq”
  4. 编写一个需要将代码作为数据返回的宏

答案 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;
  • 随机摆弄矢量元素和心灵内容
  • 将矢量复制回列表;
  • 替换外部列表中的嵌套列表。

这可能会浪费一些表现;但如果这是一个对性能敏感的操作,那么你首先要使用矢量。