Массивы
module Array (
module Ix, - экспортирует весь Ix в целях удобства
Array, array, listArray, (!), bounds, indices, elems, assocs,
accumArray, (//), accum, ixmap ) where
import Ix
infixl 9 !, //
data (Ix a) => Array a b = ... - Абстрактный
array :: (Ix a) => (a,a) -> [(a,b)] -> Array a b
listArray :: (Ix a) => (a,a) -> [b] -> Array a b
(!) :: (Ix a) => Array a b -> a -> b
bounds :: (Ix a) => Array a b -> (a,a)
indices :: (Ix a) => Array a b -> [a]
elems :: (Ix a) => Array a b -> [b]
assocs :: (Ix a) => Array a b -> [(a,b)]
accumArray :: (Ix a) => (b -> c -> b) -> b -> (a,a) -> [(a,c)]
-> Array a b
(//) :: (Ix a) => Array a b -> [(a,b)] -> Array a b
accum :: (Ix a) => (b -> c -> b) -> Array a b -> [(a,c)]
-> Array a b
ixmap :: (Ix a, Ix b) => (a,a) -> (a -> b) -> Array b c
-> Array a c
instance Functor (Array a) where ...
instance (Ix a, Eq b) => Eq (Array a b) where ...
instance (Ix a, Ord b) => Ord (Array a b) where ...
instance (Ix a, Show a, Show b) => Show (Array a b) where ...
instance (Ix a, Read a, Read b) => Read (Array a b) where ...Haskell обеспечивает индексируемые массивы, которые можно рассматривать как функции, чьи области определения изоморфны соприкасающимся подмножествам целых чисел. Функции, ограниченные таким образом, можно эффективно реализовать; в частности, программист может ожидать разумно быстрого доступа к компонентам. Чтобы гарантировать возможность такой реализации, массивы обрабатываются как данные, а не как обычные функции.
Так как большинство функций массива затрагивают класс Ix, этот модуль экспортируется из Array, чтобы не было необходимости модулям импортировать и Array, и Ix.
16.1. Создание массивов
Если a -- тип индекса, а b -- любой тип, тип массивов с индексами в a и элементами в b записывается так: Array a b. Массив может быть создан с помощью функции array. Первым аргументом array является пара границ, каждая из которых имеет тип индекса массива. Эти границы являются соответственно наименьшим и наибольшим индексами в массиве. Например, вектор с началом в 1 длины 10 имеет границы (1,10), а матрица 10 на 10 с началом в 1 имеет границы ((1,1),(10,10)).
Вторым аргументом array является список ассоциаций вида (индекс, значение). Обычно этот список выражен в виде описания элементов. Ассоциация (i, x) определяет, что значением массива по индексу i является x. Массив не определен (т.е.
), если какой-нибудь индекс в списке находится вне границ. Если какие-нибудь две ассоциации в списке имеют один и тот же индекс, значение по этому индексу не определено (т.е.
). Так как индексы должны быть проверены на наличие этих ошибок, array является строгим по аргументу границ и по индексам списка ассоциаций, но не является строгим по значениям. Таким образом, возможны такие рекуррентные отношения:
a = array (1,100) ((1,1) : [(i, i * a!(i-1)) | i <- [2..100]])
Не каждый индекс в пределах границ массива обязан появиться в списке ассоциаций, но значения, связанные с индексами, которых нет в списке, будут не определены (т.е.
). На примере 16.1 изображены некоторые примеры, которые используют конструктор array.
- Масштабирование массива чисел с помощью заданного числа:
scale :: (Num a, Ix b) => a -> Array b a -> Array b a
scale x a = array b [(i, a!i * x) | i <- range b]
where b = bounds a
- Инвертирование массива, который содержит перестановку своих индексов
invPerm :: (Ix a) => Array a a -> Array a a
invPerm a = array b [(a!i, i) | i <- range b]
where b = bounds a
- Скалярное произведение двух векторов
inner :: (Ix a, Num b) => Array a b -> Array a b -> b
inner v w = if b == bounds w
then sum [v!i * w!i | i <- range b]
else error "массивы не подходят для скалярного произведения"
where b = bounds v
Листинг
16.1.
Примеры массивов
Оператор (!) обозначает доступ к элементам массива по индексу (операция индексации массива). Функция bounds, будучи примененной к массиву, возвращает его границы. Функции indices, elems и assocs, будучи примененными к массиву, возвращают соответственно списки индексов, элементов или ассоциаций в порядке возрастания их индексов. Массив можно создать из пары границ и списка значений в порядке возрастания их индексов, используя функцию listArray.
Если в каком-либо измерении нижняя граница больше чем верхняя граница, то такой массив допустим, но он пуст. Индексация пустого массива всегда приводит к ошибке выхода за границы массива, но bounds по-прежнему сообщает границы, с которыми массив был создан.
16.1.1. Накопленные массивы
Другая функция создания массива, accumArray, ослабляет ограничение, при котором данный индекс может появляться не более одного раза в списке ассоциаций, используя функцию накопления, которая объединяет значения ассоциаций с одним и тем же индексом. Первым аргументом accumArray является функция накопления; вторым -- начальное значение; оставшиеся два аргумента являются соответственно парой границ и списком ассоциаций, как и для функции array. Например, при заданном списке значений некоторого типа индекса, hist создает гистограмму числа вхождений каждого индекса в пределах указанного диапазона:
hist :: (Ix a, Num b) => (a,a) -> [a] -> Array a b hist bnds is = accumArray (+) 0 bnds [(i, 1) | i <-is, inRange bnds i]
Если функция накопления является строгой, то accumArray является строгой в отношении значений, также как и в отношении индексов, в списке ассоциаций. Таким образом, в отличие от обычных массивов, накопленные массивы не должны быть в общем случае рекурсивными.
16.2. Добавочные обновления массивов
Оператор (//) принимает в качестве аргументов массив и список пар и возвращает массив, идентичный левому аргументу, за исключением того, что он обновлен ассоциациями из правого аргумента. (Как и с функцией array, индексы в списке ассоциаций должна быть уникальны по отношению к обновляемым элементам, которые определены.) Например, если m -- матрица n на n с началом в 1, то m//[((i,i), 0) | i <- [1..n]] -- та же самая матрица, у которой диагональ заполнена нулями.
accum f принимает в качестве аргументов массив и список ассоциаций и накапливает пары из списка в массив с помощью функции накопления f. Таким образом, accumArray можно определить через accum:
accumArray f z b = accum f (array b [(i, z) | i <- range b])
16.3. Производные массивы
Функции fmap и ixmap получают новые массивы из существующих; их можно рассматривать как обеспечение композиции функций слева и справа соответственно, с отображением, которое реализует исходный массив. Функция fmap преобразовывает значения массива, в то время как ixmap позволяет выполнять преобразования на индексах массива. На примере 16.2 изображены некоторые примеры.
- Прямоугольный подмассив
subArray :: (Ix a) => (a,a) -> Array a b -> Array a b
subArray bnds = ixmap bnds (\i->i)
- Строка матрицы
row :: (Ix a, Ix b) => a -> Array (a,b) c -> Array b c
row i x = ixmap (l',u') (\j->(i,j)) x where ((_,l'),(_,u')) = bounds x
- Диагональ матрицы (матрица предполагается квадратная)
diag :: (Ix a) => Array (a,a) b -> Array a b
diag x = ixmap (l,u) (\i->(i,i)) x
where
((l,_),(u,_)) = bounds x
- Проекция первых компонент массива пар
firstArray :: (Ix a) => Array a (b,c) -> Array a b
firstArray = fmap (\(x,y)->x)
Листинг
16.2.
Примеры производных массивов
16.4. Библиотека Array
module Array (
module Ix, - экспортировать весь Ix
Array, array, listArray, (!), bounds, indices, elems, assocs,
accumArray, (//), accum, ixmap ) where
import Ix
import List( (\\) )
infixl 9 !, //
data (Ix a) => Array a b = MkArray (a,a) (a -> b) deriving ()
array :: (Ix a) => (a,a) -> [(a,b)] -> Array a b
array b ivs =
if and [inRange b i | (i,_) <- ivs]
then MkArray b
(\j -> case [v | (i,v) <- ivs, i == j] of
[v] -> v
[] -> error "Array.!: \
\неопределенный элемент массива"
_ -> error "Array.!: \
\множественно определенный элемент массива")
else error "Array.array: ассоциация массива находится за пределами диапазона"
listArray :: (Ix a) => (a,a) -> [b] -> Array a b
listArray b vs = array b (zipWith (\ a b -> (a,b)) (range b) vs)
(!) :: (Ix a) => Array a b -> a -> b
(!) (MkArray _ f) = f
bounds :: (Ix a) => Array a b -> (a,a)
bounds (MkArray b _) = b
indices :: (Ix a) => Array a b -> [a]
indices = range . bounds
elems :: (Ix a) => Array a b -> [b]
elems a = [a!i | i <- indices a]
assocs :: (Ix a) => Array a b -> [(a,b)]
assocs a = [(i, a!i) | i <- indices a]
(//) :: (Ix a) => Array a b -> [(a,b)] -> Array a b
a // new_ivs = array (bounds a) (old_ivs ++ new_ivs)
where
old_ivs = [(i,a!i) | i <- indices a,
i `notElem` new_is]
new_is = [i | (i,_) <- new_ivs]
accum :: (Ix a) => (b -> c -> b) -> Array a b -> [(a,c)]
-> Array a b
accum f = foldl (\a (i,v) -> a // [(i,f (a!i) v)])
accumArray :: (Ix a) => (b -> c -> b) -> b -> (a,a) -> [(a,c)]
-> Array a b
accumArray f z b = accum f (array b [(i,z) | i <- range b])
ixmap :: (Ix a, Ix b) => (a,a) -> (a -> b) -> Array b c
-> Array a c
ixmap b f a = array b [(i, a ! f i) | i <- range b]
instance (Ix a) => Functor (Array a) where
fmap fn (MkArray b f) = MkArray b (fn . f)
instance (Ix a, Eq b) => Eq (Array a b) where
a == a' = assocs a == assocs a'
instance (Ix a, Ord b) => Ord (Array a b) where
a <= a' = assocs a <= assocs a'
instance (Ix a, Show a, Show b) => Show (Array a b) where
showsPrec p a = showParen (p > arrPrec) (
showString "array " .
showsPrec (arrPrec+1) (bounds a) . showChar ' ' .
showsPrec (arrPrec+1) (assocs a) )
instance (Ix a, Read a, Read b) => Read (Array a b) where
readsPrec p = readParen (p > arrPrec)
(\r -> [ (array b as, u)
| ("array",s) <- lex r,
(b,t) <- readsPrec (arrPrec+1) s,
(as,u) <- readsPrec (arrPrec+1) t ])
- Приоритет функции 'array' -- тот же, что и приоритет самого применения функции
arrPrec = 10