Clojure: Как заменить элемент во вложенном списке? - PullRequest
7 голосов
/ 07 декабря 2009

У меня есть этот глубоко вложенный список (список списков), и я хочу заменить один произвольный элемент в списке. Как я могу это сделать ? (Встроенная замена может заменить многие вхождения, в то время как мне нужно заменить только один элемент.)

Ответы [ 5 ]

9 голосов
/ 07 декабря 2009

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

В некоторых случаях я бы использовал список для данных:

  1. Если у меня есть упорядоченная структура данных, которая должна расти с фронта, мне никогда не понадобится произвольный доступ к
  2. Построение последовательности "вручную", как через lazy-seq
  3. Написание макроса, который должен возвращать код в виде данных
6 голосов
/ 07 декабря 2009

Для простых случаев функция рекурсивного замещения даст вам именно то, что вам нужно, без значительной дополнительной сложности. когда все становится немного сложнее, настало время взломать открытую сборку clojure в функциях zipper : «Clojure включает в себя чисто функциональную, общую обработку дерева и редактирование с использованием техники, называемой молнией namespace 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), даже xmls

5 голосов
/ 07 декабря 2009

Это вроде не отвечает на ваш вопрос, но если у вас есть векторы вместо списков:

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 из разных источников, это, конечно, не очень хороший совет ...

0 голосов
/ 08 декабря 2009

Простое предложение из галереи арахиса:

  • скопировать внутренний список в вектор;
  • случайным образом подгоняй элементы этого вектора к своему сердцу, используя assoc;
  • скопировать вектор обратно в список;
  • заменить вложенный список во внешнем списке.

Это может привести к потере производительности; но если бы это была чувствительная к производительности операция, вы бы сначала работали с векторами.

0 голосов
/ 07 декабря 2009

Вы можете использовать эту функцию и адаптировать ее для своих нужд (вложенные списки):

(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)))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...