Идиоматическое повторение 2-х (или более высокой) размерной последовательности в Clojure

#clojure

#clojure

Вопрос:

Существует ли «правильный» способ повторения двумерной последовательности в Clojure? Предположим, у меня был список списков чисел, подобный этому

  ((1 2 3)
  (4 5 6)
  (7 8 9))
  

и я хотел сгенерировать новый список списков с каждым числом, увеличенным на единицу. Есть ли простой способ сделать это в Clojure, не полагаясь на вложенные карты или цикл / повторения? Я смог это сделать, но мои решения уродливы, и мне трудно их понять, когда я их перечитываю.

Спасибо

Ответ №1:

То, что вы описываете, — это именно то, clojure.walk для:

(матрица определения [[1 2 3]
 [4 5 6]
 [7 8 9]])
(используйте 'clojure.прохождение: только [предварительный просмотр])
(предварительный переход #(если (число? %) (включая %) %) матрицу)
=> [[2 3 4] [5 6 7] [8 9 10]]

Примечание 1: использование векторов вместо круглых скобок для буквальных последовательных коллекций является идиоматичным.

Примечание 2: переход сохраняет тип.

Комментарии:

1. Спасибо, что упомянули примечание 1 — я все еще немного сомневаюсь, когда использовать то или иное.

2. На этот ответ ссылается шпаргалка Clojure по адресу clojure.org.

Ответ №2:

Вы всегда можете просто использовать понимание списка. Я обнаружил, что использую их довольно часто, исходя из императивного фона, поэтому я не знаю, насколько это идиоматично. В вашем конкретном случае вы можете сделать:

 (for [my-list my-matrix] (map inc my-list))
  

Комментарии:

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

2. Следует отметить, что for создает отложенную последовательность, поэтому она «повторяется» только тогда, когда запрашивается значение.

Ответ №3:

Для двумерного случая вы могли бы сделать что-то вроде:

 (map #(map inc %) my-two-d-list)
  

Это не так уж плохо для чтения: примените функцию #(map inc %) к каждому элементу в списке.

В случае более высокого порядка вы в основном говорите о обходе по дереву. Вам нужна функция, которая принимает дерево и функцию и применяет эту функцию к каждому узлу в дереве. Вы можете найти функции для этого в clojure.walk.

Ответ №4:

Другие ответы Шона и Мэтта показывают краткие и эффективные способы получения правильного результата.

Однако есть несколько важных расширений, которые вы можете внести в это:

  • Было бы неплохо обработать случай более высоких измерений
  • Хорошо бы обернуть функциональность в функцию более высокого порядка

Пример кода:

 ;; general higher order function
(defn map-dimensions [n f coll] 
  (if (= n 1)
    (map f coll)
    (map #(map-dimensions (dec n) f %) coll)))

;; use partial application to specialise to 2 dimensions
(def map-2d (partial map-dimensions 2))

(map-2d inc  
    '((1 2 3)
      (4 5 6)
      (7 8 9)))
=> ((2 3 4) (5 6 7) (8 9 10))
  

Комментарии:

1. Тем не менее, полезно посмотреть, как это можно сделать с нуля.

2. Также prewalk требуется функция, которая отличает конечные узлы от узлов ответвления. Вы не можете просто дать inc , например.

Ответ №5:

С момента появления core.matrix в 2013 году это теперь гораздо лучший способ обработки операций над многомерными массивами:

 (use 'clojure.core.matrix)

(def M  [[1 2 3]
         [4 5 6]
         [7 8 9]])

(emap inc M)

=> [[2 3 4 ]
    [5 6 7 ]
    [8 9 10]]
  

Преимущества использования core.matrix :

  • Чистый, идиоматичный код Clojure
  • Множество функций управления n-мерным массивом общего назначения — transpose , shape , reshape , slice subarray ,,, и т.д.
  • Возможность подключения высокопроизводительных реализаций массива (например, для больших числовых массивов)

Ответ №6:

Запоздалый ответ и, возможно, не совсем то, что нужно: вы могли бы попробовать сгладить. Это вернет последовательность, которую вы можете повторить:

 (flatten  '((1 2 3)
            (4 5 6)
            (7 8 9)))

user=> (1 2 3 4 5 6 7 8 9)
  

И для того, чтобы увеличивать элементы матрицы и повторно собирать матрицу:

 (partition 3 (map inc (flatten  '((1 2 3)
                                  (4 5 6)
                                  (7 8 9)))))