代码之家  ›  专栏  ›  技术社区  ›  GabiMe

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

  •  7
  • GabiMe  · 技术社区  · 15 年前

    我有一个深度嵌套的列表(列表列表),我想替换列表中的一个任意元素。我该怎么做?(内置的replace可能会替换多次,而我只需要替换一个元素。)

    5 回复  |  直到 15 年前
        1
  •  9
  •   Brian Carper    15 年前

    正如其他人已经说过的,如果你需要做这种事情,使用列表实际上不是一个好主意。随机存取就是向量的作用。 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
    

    你也不必写 在美国 你自己,它已经存在了。查看的实现 在美国 有时,它是简单和直接的(与列表版本相比),这要归功于向量,通过 get .

    您也不必像引用列表那样引用向量。Clojure中的列表强烈暗示“我在这里调用函数或宏”。

    矢量(以及地图、集合等)可以通过 seq 你可以像列表一样透明地使用向量,那么为什么不使用向量,并且两者兼备呢?

    向量在视觉上也很突出。由于广泛使用 [] {} .有些人觉得这很烦人,我觉得它使阅读更容易。(我的编辑器语法突出显示 () , [] {} 不同的,这会有更多的帮助。)

    某些情况下,我会使用数据列表:

    1. 如果我有一个需要从前端增长的有序数据结构,我就不需要随机访问
    2. 构建“手动”序列,如通过 lazy-seq
    3. 编写宏,需要将代码作为数据返回
        2
  •  6
  •   Arthur Ulfeldt    15 年前

    对于简单的情况,递归替换函数将为您提供所需的东西,并且会带来更多的复杂性。当事情变得更复杂时,是时候打开Clojure了。 zipper 功能:“Clojure包含纯功能, 通用树漫游和编辑 ,使用名为zipper的技术(在命名空间zip中)。”

    改编自以下示例: 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)甚至XML一起工作

        3
  •  5
  •   kotarak    15 年前

    它没有回答你的问题,但是如果你有向量而不是列表:

    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,这当然不是什么建议…

        4
  •  0
  •   dermatthias    15 年前

    您可以使用此函数并根据需要对其进行调整(嵌套列表):

    (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)))
    
        5
  •  0
  •   Carl Smotricz    15 年前

    花生画廊的简单建议:

    • 将内部列表复制到向量;
    • 随机摆弄向量的元素,并使用 assoc ;
    • 将向量复制回列表;
    • 替换外部列表中的嵌套列表。

    这可能会浪费一些性能;但是如果这是一个性能敏感的操作,那么您首先应该使用向量。