Clojure: Как заменить элемент во вложенном списке?

Это не волшебство или что-либо... просто приятная стенография для анонимных функций.

partial(alert, "FOO!") эквивалентно function(){alert("FOO!");}

partial(Math.max, 0), соответствует function(x){return Math.max(0, x);}

вызовы к частичному ( терминология MochiKit . Я думаю, что некоторые другие библиотеки дают функциям .curry метод, который делает то же самое), выглядят немного более хорошими и менее шумными, чем анонимные функции.

7
задан GabiMe 7 December 2009 в 11:49
поделиться

5 ответов

Как уже было сказано, использование списков - не лучшая идея, если вам нужно делать такие вещи. Произвольный доступ - это то, для чего созданы векторы. Assoc-in делает это эффективно. Со списками вы не можете избежать рекурсии вниз в подсписки и замены большинства из них измененными версиями самих себя вплоть до самого верха.

Этот код сделает это, хотя и неэффективно и неуклюже. Заимствование из dermatthias:

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

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

  1. Если у меня есть упорядоченная структура данных, которая должна расти спереди, мне никогда не понадобится произвольный доступ для
  2. Создание seq «вручную», как через lazy-seq
  3. Написание макроса, который должен возвращать код в виде данных
9
ответ дан 6 December 2019 в 09:20
поделиться

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

6
ответ дан 6 December 2019 в 09:20
поделиться

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

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

5
ответ дан 6 December 2019 в 09:20
поделиться

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

(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)))
0
ответ дан 6 December 2019 в 09:20
поделиться

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

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

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

0
ответ дан 6 December 2019 в 09:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: