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

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

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

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

Спасибо

Ответ 1

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

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

Ответ 2

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

(def matrix [[1 2 3]
             [4 5 6]
             [7 8 9]])
(use 'clojure.walk :only [prewalk])
(prewalk #(if (number? %) (inc %) %) matrix)
=> [[2 3 4] [5 6 7] [8 9 10]]

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

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

Ответ 3

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

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

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

Для случая более высокого порядка вы в основном говорите об обходе дерева. Вам нужна функция, которая принимает дерево и функцию, и применяет эту функцию к каждому node в дереве. Вы можете найти функции для этого в 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))

Ответ 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. Он вернет seq, который вы можете перебрать:

(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)))))