Как все уже говорили, использование списков не очень хорошая идея, если вам нужно делать подобные вещи. Случайный доступ - это то, для чего созданы векторы. 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
s. Вы можете прозрачно использовать векторы в виде списков, так почему бы не использовать векторы и иметь лучшее из обоих миров?
Векторы также выделяются визуально. Код Clojure - не такая большая капля паренсов, как другие Лиспы, благодаря широкому использованию []
и {}
. Некоторые люди находят это раздражающим, я считаю, что это облегчает чтение. (Мой редактор синтаксис выделяет ()
, []
и {}
по-разному, что помогает еще больше.)
В некоторых случаях я бы использовал список для данных:
- Если у меня есть упорядоченная структура данных, которая должна расти с фронта, мне никогда не понадобится произвольный доступ к
- Построение последовательности "вручную", как через
lazy-seq
- Написание макроса, который должен возвращать код в виде данных