轻松更改列表中的特定列表项

时间:2016-11-01 23:33:36

标签: clojure

在Clojure中我希望在列表中更改特定项目(列表)与其他。 这是我的结构:

(def myLis '((3 3) (5 5) (5 5)))
(def som '(4 4))

我希望在myLis中使用som更改第二个元素。 myLis中的结果为'((3 3) (4 4) (5 5)) 这是基本的例子。我在myList中有几百个项目。 我尝试关联和更新,但这不适用于列表。

当我尝试使用assoc并更新时:

(update-in myLis [1] som)
(assoc myLis 1 som)
(assoc-in myLis [1] som)

有这样的错误:

clojure.lang.PersistentList cannot be cast to clojure.lang.Associative

如何快速更改此结构中的第n个元素(列表列表)。

4 个答案:

答案 0 :(得分:2)

对于大多数用途,您通常应优先使用[1 2 3]等向量,而不是像'(1 2 3)这样的列表。在Clojure中,列表通常用于函数调用,如(+ 1 2),而数据文字向量通常用于[1 2 3]

以下代码显示了两个有用的选项。

主要代码:

(ns clj.core
  (:require 
    [tupelo.core :as t]
  ))
(t/refer-tupelo)

(def myLis [ [3 3] [5 5] [5 5] ] )
(def som [4 4] )

(spyx (assoc      myLis  1  som))
(spyx (assoc-in   myLis [1] som))

(defn -main [& args]
  (println "-main"))

结果:

~/clj > lein run    
(assoc myLis 1 som)      => [[3 3] [4 4] [5 5]]
(assoc-in myLis [1] som) => [[3 3] [4 4] [5 5]]

您需要在project.clj中进行此操作才能使(spy ...)正常工作:

:dependencies [
  [tupelo "0.9.9"] 
  ...

更新2016-11-2:

如果您确实要将所有内容保留在列表中,可以使用replace-at from the Tupelo library。它的工作原理如下:

(def myLis '( (3 3) (5 5) (5 5) ) )
(def vec-1    [4 4] )
(def list-1  '(4 4) )

(spyx             (t/replace-at myLis 1 vec-1 ))
(spyx             (t/replace-at myLis 1 list-1))
(spyx (apply list (t/replace-at myLis 1 list-1)))

结果

> lein run
(t/replace-at myLis 1 vec-1)               => [(3 3) [4 4] (5 5)]
(t/replace-at myLis 1 list-1)              => [(3 3) (4 4) (5 5)]
(apply list (t/replace-at myLis 1 list-1)) => ((3 3) (4 4) (5 5))

前两个示例显示新元素可以是任何内容,例如向量[4 4]或列表(4 4)。另请注意,replace-at始终返回矢量结果。如果您希望最终结果也是列表,则需要使用(apply list <some-collection>)

答案 1 :(得分:2)

正如clojure圣经(Clojure Programming)所指出的那样:

  

因为[lists]是链表,所以它们不支持高效随机   访问;因此,列表中的第n个将以线性时间运行(而不是   与矢量,数组等一起使用时的常数时间,并得到它   根本不支持列表,因为这样做不会与get一致   次线性效率的目标。

因此,为了替换列表中的元素,您必须遍历所有元素,因此项目在列表中的运行时间越长,并使用之前的元素重新构建列表,新项目以及之后的所有元素(休息)。或者,将列表转换为向量,如果绝对必须使用列表,请使用update-in并返回列表。

但是,如果可以,那么您是否可以在代码而不是列表中使用序列是值得的,因此您可以互换使用向您执行的处理更有效的向量或其他抽象。< / p>

但是,通过列表满足您的要求基础的一个简单的功能是:

(defn list-update-in [l i x]
  (let [newlist (take i l)
        newlist (concat newlist (list x))
        newlist (concat newlist (drop (+ 1 i) l))]
    newlist))

user> (list-update-in '((1 2) (2 3) (3 4)) 1 '(8 9))
((1 2) (8 9) (3 4))

没有越界检查

答案 2 :(得分:1)

使用loop / recur添加一个额外的答案,以重新调整OP自己的解决方案,使其更像lisp。

(defn list-update-in-recur [l i x]
  (loop [new-data [] old-list l]
    (if (seq old-list)
      (if (= (count new-data) i)
        (recur (conj new-data x) (rest old-list))
        (recur (conj new-data (first old-list)) (rest old-list)))
      (apply list new-data))))

user> (list-update-in-recur '((1 2) (2 3) (3 4)) 1 '(8 9))
((1 2) (8 9) (3 4))

需要注意几点:

  1. 它是作为一个函数编写的,没有&#39; def&#39;用于设置任何全局值的值。最终结果是返回函数(apply list new-data)
  2. 参数初始化循环,增长列表的大小用于确定是否要替换第n个项目(无索引变量)
  3. 传入的列表成为初始old-list值,每次迭代都会减小,并且退出条件只是在使用测试(seq old-list)时还剩下任何元素时返回false / n为空时。
  4. 因为我们conj所有内容(添加到列表的开头)我们将其反转以返回输出。它现在使用向量来创建新序列,并转换为将列表作为最后一步而不是反转列表
  5. 我已将nth替换为firstrest,效率更高,并且每次迭代都不必遍历整个列表。
  6. 这仍然非常低效,只能作为学习练习。

答案 3 :(得分:0)

我的解决方案包含列表:

(def newL '())
(def i 1)
(loop [k (- (count myLis) 1)]
  (when (> k -1)
    (cond
      (= k i) (def newL (conj newL som))
      :else (def newL (conj newL (nth myLis k)))
    )
    (recur (- k 1))
  )
)