N-мерный массив с Haskell

#arrays #haskell

Вопрос:

В Python мы можем создать индекс a numpy.ndarray с кортежами, например

 cube = numpy.zeros((3,3,3,))
print(cube[(0,1,2,)])
 

. Однако в Haskell для индексации многослойного массива это можно сделать только с помощью нескольких !! ‘s, что кажется довольно сложным.

Я пытался foldl :

 foldl

  (!!)

  [[[1, 2, ....], [1, 2, ....], [1, 2, ....]],
   [[1, 2, ....], [1, 2, ....], [1, 2, ....]],
   [[1, 2, ....], [1, 2, ....], [1, 2, ....]]]

  [0, 1, 2]
 

Однако foldl может применяться только к таким функциям , как a -> b -> a , not [a] -> b -> a . Некоторые другие информационные шоу hmatrix могут делать то же numpy самое, что и в python, но это применимо только к матрицам и векторам, где размерность не регулируется.

Это всегда можно сделать с помощью индексации в стиле C, т. Е. Поместить все данные в одномерный список и проиндексировать их с помощью умножения, 0 1*3 2*9 , но это кажется грубым, приводит к потере информации об измерениях и приведет к тому, что компилятор не сможет настроить их в правильном порядке.

Как это сделать более абстрактным способом?

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

1. Что вас останавливает index xs i j k = xs !! i !! j !! k ?

2. Посмотрите на massiv , это, вероятно, самая полная реализация многомерных массивов на сегодняшний день.

3. Это не массив, это список, и вам не следует полагаться на него, (!!) поскольку 1) он медленный (O (n), а не O (1), как это было бы для подлинного массива) 2) это небезопасно (если вы попытаетесь получить доступ к индексу, который неприсутствует в списке, вы получаете сбой во время выполнения).

Ответ №1:

Из вопроса мне не совсем понятно, чего вы пытаетесь достичь, но если ваш вопрос касается только индексации многомерных массивов в Haskell, я постараюсь ответить на него в меру своих возможностей. Спасибо @leftaroundabout за предложение massiv в разделе комментариев, будучи автором этой библиотеки, я склонен согласиться с его комментарием.

Одно можно сказать наверняка: по нескольким причинам вы не хотите использовать вложенные списки для создания массивов. Сложность линейной индексации и низкая производительность — лишь некоторые из этих причин.

Построение массива

Давайте посмотрим, как мы можем это сделать massiv . Сначала я переведу ваш numpy пример:

 cube :: Array P Ix3 Float
cube = A.replicate Seq (Sz (3 :> 3 :. 3)) 0
 

Обратите внимание, поскольку у нас на самом деле есть типы в Haskell, нам нужно сделать некоторые примечания к тому, какой тип массива мы пытаемся создать, например. упакованный против распакованного, изменяемый против неизменяемого и т. Д. Я рекомендую ознакомиться с документацией библиотеки, чтобы получить больше информации по этим темам. Здесь я сосредоточусь на индексах, поскольку именно об этом и идет речь. Чтобы получить элемент из вышеупомянутого 3D-массива на 0-й странице, 2-й строке и 3-м столбце ( cube[(0,1,2,)] numpy пример from), мы можем использовать оператор времени O (1) ! с индексом, указанным в его правой части:

 λ> cube ! (0 :> 1 :. 2)
0.0
 

Обратите внимание, что оператор индексирования ! является частичным и приведет к исключению во время выполнения при выходе за пределы:

 λ> cube ! (10 :> 1 :. 2)
*** Exception: IndexOutOfBoundsException: (10 :> 1 :. 2) is not safe for (Sz (3 :> 3 :. 3))
CallStack (from HasCallStack):
  throwEither, called at src/Data/Massiv/Core/Common.hs:807:11 in massiv-1.0.1.0-...
 

Чего можно легко избежать с помощью более безопасного варианта !? :

 λ> cube !? (0 :> 1 :. 2) :: Maybe Float
Just 0.0
λ> cube !? (10 :> 1 :. 2) :: Maybe Float
Nothing
 

Синтаксис индекса

Так же, как и в случае с numpy , можно использовать кортежи для индексации massiv массивов, но поскольку кортежи полиморфны, проверяющему типы иногда сложнее сделать правильный вывод, кроме того, кортежи поддерживаются massiv только в 5 измерениях. Поэтому вместо этого я покажу примеры для Ix n типа, где n — количество измерений, которое может быть произвольным.

При работе с плоскими векторами Int для индексации используется regular (соответствует Ix 1 ):

 λ> let vec = makeVectorR P Seq (Sz 10) id
λ> vec
Array P Seq (Sz1 10)
  [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
λ> vec ! 7
7
 

Для двух измерений существует специальный оператор :. (соответствует Ix 2 ):

 λ> let mat = makeArrayR P Seq (Sz (2 :. 10)) $ (i :. j) -> i   j
λ> mat
Array P Seq (Sz (2 :. 10))
  [ [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
  , [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
  ]
λ> mat ! (1 :. 3)
4
 

Индекс для любого измерения больше 2 строится с :> помощью operator (соответствует Ix n ):

 λ> let arr3D = makeArrayR P Seq (Sz (3 :> 2 :. 1)) $ (i :> j :. k) -> i   j   k
λ> arr3D ! (2 :> 1 :. 0)
3
λ> let arr4D = makeArrayR P Seq (Sz (4 :> 3 :> 2 :. 1)) $ (h :> i :> j :. k) -> h   i   j   k
λ> arr4D ! (3 :> 2 :> 1 :. 0)
6
 

Более подробную информацию об индексах с примерами можно найти в разделе #index на README.