Многомерные массивы
Julia, как и большинство языков технических вычислений, предоставляет реализацию массивов первого класса. Большинство языков технических вычислений уделяют много внимания реализации массивов в ущерб другим контейнерам. Julia не относится к массивам каким-либо особым образом. Библиотека массивов реализована почти полностью на самом Julia и черпает свою производительность из компилятора, как и любой другой код, написанный на Julia. Таким образом, также можно определить пользовательские типы массивов, унаследовавшись от AbstractArray. Более подробную информацию об реализации пользовательского типа массива см. в разделе руководства по интерфейсу AbstractArray.
Массив представляет собой коллекцию объектов, хранящихся в многомерной сетке. В наиболее общем случае массив может содержать объекты типа Any. Для большинства вычислительных целей массивы должны содержать объекты более конкретного типа, такие как Float64 или Int32.
В целом, в отличие от многих других языков технических вычислений, Julia не ожидает, что программы будут написаны в векторном стиле для повышения производительности. Компилятор Julia использует вывод типов и генерирует оптимизированный код для индексирования массивов скалярами, что позволяет писать программы удобным и читабельным стилем без ущерба для производительности и иногда с меньшим потреблением памяти.
Во всех функциях Julia все аргументы передаются по ссылке (т. е. по указателям). Некоторые языки технических вычислений передают массивы по значению, и хотя это предотвращает случайное изменение значения вызываемой функцией из вызывающей функции, это затрудняет избегание нежелательных копий массивов. По соглашению, имя функции, оканчивающееся на !, указывает на то, что она будет изменять или уничтожать значение одного или нескольких своих аргументов (например, сравните sort и sort!). Вызываемая функция должна явно создавать копии, чтобы убедиться, что она не изменяет входные данные, которые она не намерена изменять. Многие не изменяющие функции реализуются путем вызова функции с таким же именем и добавленным ! в конце на явной копии входных данных и возвращением этой копии.
Основные функции
| Функция | Описание |
|---|---|
eltype(A) |
тип элементов, содержащихся в A
|
length(A) |
количество элементов в A
|
ndims(A) |
количество измерений A
|
size(A) |
кортеж, содержащий размеры A
|
size(A,n) |
размер A по измерению n
|
axes(A) |
кортеж, содержащий допустимые индексы A
|
axes(A,n) |
диапазон, выражающий допустимые индексы по измерению n
|
eachindex(A) |
эффективный итератор для посещения каждой позиции в A
|
stride(A,k) |
шаг (расстояние линейного индекса между соседними элементами) по измерению k
|
strides(A) |
кортеж шагов по каждому измерению |
Создание и инициализация
Предоставляется множество функций для создания и инициализации массивов. В приведенном ниже списке таких функций вызовы с аргументом dims... могут принимать либо один кортеж размеров измерений, либо серию размеров измерений, передаваемых в виде переменного числа аргументов. Большинство этих функций также принимают первый вход T, который является типом элементов массива. Если тип T опущен, он по умолчанию будет Float64.
| Функция | Описание |
|---|---|
Array{T}(undef, dims...) |
неинициализированная плотная Array
|
zeros(T, dims...) |
массив из нулей |
ones(T, dims...) |
массив из единиц |
trues(dims...) |
массив BitArray со всеми значениями true
|
falses(dims...) |
массив со всеми значениями BitArray false
|
reshape(A, dims...) |
массив, содержащий те же данные, что и A, но с другими размерами |
copy(A) |
копирование A
|
deepcopy(A) |
копирование A, рекурсивно копируя его элементы |
similar(A, T, dims...) |
неинициализированный массив того же типа, что и A (плотный, разреженный и т. д.), но со специфицированным типом элемента и размерами. Второй и третий аргументы являются необязательными и по умолчанию принимают тип элемента и размеры A в случае опускания. |
reinterpret(T, A) |
массив с теми же двоичными данными, что и A, но с типом элемента T
|
rand(T, dims...) |
массив со случайными, независимыми и одинаково распределенными значениями в полуоткрытом интервале $[0, 1)$ |
randn(T, dims...) |
массив со случайными, независимыми и стандартно распределенными значениями |
Matrix{T}(I, m, n) |
m×n единичная матрица |
range(start, stop=stop, length=n) |
диапазон из n линейно расположенных элементов от start до stop
|
fill!(A, x) |
заполнить массив A значением x
|
fill(x, dims...) |
массив, заполненный значением Array x
|
iid, независимые и одинаково распределенные.
Синтаксис [A, B, C, ...] создает одномерный массив (т. е. вектор) из своих аргументов. Если у всех аргументов есть общий тип продвижения, они преобразуются к этому типу с помощью convert.
Чтобы увидеть различные способы передачи размеров в эти конструкторы, рассмотрите следующие примеры:
julia> zeros(Int8, 2, 3)
2×3 Array{Int8,2}:
0 0 0
0 0 0
julia> zeros(Int8, (2, 3))
2×3 Array{Int8,2}:
0 0 0
0 0 0
julia> zeros((2, 3))
2×3 Array{Float64,2}:
0.0 0.0 0.0
0.0 0.0 0.0
Здесь (2, 3) является Tuple.
Конкатенация
Массивы могут быть созданы и конкатенированы с помощью следующих функций:
| Функция | Описание |
|---|---|
cat(A...; dims=k) |
конкатенация входных массивов по измерению(ям) k
|
vcat(A...) |
краткая запись для cat(A...; dims=1)
|
hcat(A...) |
краткая запись для cat(A...; dims=2)
|
Скалярные значения, передаваемые в эти функции, обрабатываются как массивы из одного элемента. Например,
julia> vcat([1, 2], 3)
3-element Array{Int64,1}:
1
2
3
julia> hcat([1 2], 3)
1×3 Array{Int64,2}:
1 2 3
Функции конкатенации используются так часто, что имеют специальный синтаксис:
| Выражение | Вызовы |
|---|---|
[A; B; C; ...] |
vcat |
[A B C ...] |
hcat |
[A B; C D; ...] |
hvcat |
hvcat склеивает по обеим размерностям (размерность 1 с точками с запятой, размерность 2 с пробелами). Рассмотрим примеры этого синтаксиса:
julia> [[1; 2]; [3, 4]]
4-element Array{Int64,1}:
1
2
3
4
julia> [[1 2] [3 4]]
1×4 Array{Int64,2}:
1 2 3 4
julia> [[1 2]; [3 4]]
2×2 Array{Int64,2}:
1 2
3 4
Инициализаторы типизированных массивов
Массив с определённым типом элементов можно создать, используя синтаксис T[A, B, C, ...]. Это создаст одномерный массив с типом элементов T, инициализированный элементами A, B, C, и т. д. Например, Any[x, y, z] создаёт гетерогенный массив, который может содержать любые значения.
Синтаксис конкатенации аналогичным образом можно префиксровать типом, чтобы указать тип элементов результата.
julia> [[1 2] [3 4]]
1×4 Array{Int64,2}:
1 2 3 4
julia> Int8[[1 2] [3 4]]
1×4 Array{Int8,2}:
1 2 3 4
Операторы сжатия
Операторы сжатия предлагают общий и мощный способ построения массивов. Синтаксис операторов сжатия похож на обозначение множеств в математике:
A = [ F(x,y,...) for x=rx, y=ry, ... ]
Значение этой формы состоит в том, что F(x,y,...) вычисляется с переменными x, y, и т. д., принимая каждое значение из их списка значений. Значения могут быть указаны как любые итерируемые объекты, но обычно являются диапазонами, такими как 1:n или 2:(n-1), или явными массивами значений, такими как [1.2, 3.4, 5.7]. Результатом является N-мерный плотный массив с размерностями, которые являются конкатенацией размерностей диапазонов переменных rx, ry, и т. д., и каждое F(x,y,...) вычисление возвращает скаляр.
В следующем примере вычисляется взвешенное среднее текущего элемента и его левого и правого соседей вдоль одномерной сетки.:
julia> x = rand(8)
8-element Array{Float64,1}:
0.843025
0.869052
0.365105
0.699456
0.977653
0.994953
0.41084
0.809411
julia> [ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]
6-element Array{Float64,1}:
0.736559
0.57468
0.685417
0.912429
0.8446
0.656511
Тип результирующего массива зависит от типов вычисленных элементов. Для явного управления типом можно добавить тип перед оператором сжатия. Например, мы могли запросить результат в одинарной точности, написав:
Float32[ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]
Выражения-генераторы
Операторы сжатия также могут быть записаны без заключительных квадратных скобок, создавая объект, известный как генератор. Этот объект может быть итерирован для получения значений по требованию, вместо выделения массива и хранения их заранее (см. Итерация). Например, следующее выражение суммирует ряд без выделения памяти:
julia> sum(1/n^2 for n=1:1000) 1.6439345666815615
При записи выражения генератора с несколькими размерностями внутри списка аргументов необходимы круглые скобки для отделения генератора от последующих аргументов:
julia> map(tuple, 1/(i+j) for i=1:2, j=1:2, [1:4;]) ERROR: syntax: invalid iteration specification
Все выражения, разделенные запятыми после for интерпретируются как диапазоны. Добавление круглых скобок позволяет добавить третий аргумент к map:
julia> map(tuple, (1/(i+j) for i=1:2, j=1:2), [1 3; 2 4])
2×2 Array{Tuple{Float64,Int64},2}:
(0.5, 1) (0.333333, 3)
(0.333333, 2) (0.25, 4)
Генераторы реализуются с помощью внутренних функций. Как и внутренние функции, используемые в других частях языка, переменные из окружающего пространства могут быть «захвачены» внутренней функцией. Например, sum(p[i] - q[i] for i=1:n) захватывает три переменные p, q и n из окружающего пространства. Захваченные переменные могут представлять проблемы с производительностью; см. советы по производительности.
Диапазоны в генераторах и операторах сжатия могут зависеть от предыдущих диапазонов путём записи нескольких ключевых слов for:
julia> [(i,j) for i=1:3 for j=1:i]
6-element Array{Tuple{Int64,Int64},1}:
(1, 1)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
(3, 3)
В таких случаях результат всегда одномерный.
Сгенерированные значения могут быть отфильтрованы с помощью ключевого слова if:
julia> [(i,j) for i=1:3 for j=1:i if i+j == 4]
2-element Array{Tuple{Int64,Int64},1}:
(2, 2)
(3, 1)
Индексирование
Общий синтаксис индексирования n-мерного массива A:
X = A[I_1, I_2, ..., I_n]
где каждый I_k может быть целочисленным скаляром, массивом целых чисел или любым другим поддерживаемым индексом. Это включает Colon (:) для выбора всех индексов в данной размерности, диапазоны вида a:c или a:b:c для выбора смежных или с шагом подсекций, и массивы булевых значений для выбора элементов по своим true индексам.
Если все индексы являются скалярами, то результат X — это единственный элемент из массива A. В противном случае X — это массив с таким же числом размерностей, как сумма размерностей всех индексов.
Если все индексы I_k являются векторами, например, то форма X будет (length(I_1), length(I_2), ..., length(I_n)), где расположение i_1, i_2, ..., i_n массива X содержит значение A[I_1[i_1], I_2[i_2], ..., I_n[i_n]].
Пример:
julia> A = reshape(collect(1:16), (2, 2, 2, 2))
2×2×2×2 Array{Int64,4}:
[:, :, 1, 1] =
1 3
2 4
[:, :, 2, 1] =
5 7
6 8
[:, :, 1, 2] =
9 11
10 12
[:, :, 2, 2] =
13 15
14 16
julia> A[1, 2, 1, 1] # all scalar indices
3
julia> A[[1, 2], [1], [1, 2], [1]] # all vector indices
2×1×2×1 Array{Int64,4}:
[:, :, 1, 1] =
1
2
[:, :, 2, 1] =
5
6
julia> A[[1, 2], [1], [1, 2], 1] # a mix of index types
2×1×2 Array{Int64,3}:
[:, :, 1] =
1
2
[:, :, 2] =
5
6
Обратите внимание, как размер результирующего массива отличается в последних двух случаях.
Если I_1 изменено на двумерную матрицу, то X станет n+1-мерным массивом формы (size(I_1, 1), size(I_1, 2), length(I_2), ..., length(I_n)). Матрица добавляет размерность.
Пример:
julia> A = reshape(collect(1:16), (2, 2, 2, 2));
julia> A[[1 2; 1 2]]
2×2 Array{Int64,2}:
1 2
1 2
julia> A[[1 2; 1 2], 1, 2, 1]
2×2 Array{Int64,2}:
5 6
5 6
Расположение i_1, i_2, i_3, ..., i_{n+1} содержит значение в A[I_1[i_1, i_2], I_2[i_3], ..., I_n[i_{n+1}]]. Все размерности, проиндексированные скалярами, отбрасываются. Например, если J — массив индексов, то результатом A[2, J, 3] является массив размером size(J). Его j-й элемент заполняется A[2, J[j], 3].
В качестве специальной части этого синтаксиса, ключевое слово end может использоваться для представления последнего индекса каждой размерности внутри скобок индексирования, определяемых размером самого внутреннего индексируемого массива. Синтаксис индексирования без ключевого слова end эквивалентен вызову getindex:
X = getindex(A, I_1, I_2, ..., I_n)
Пример:
julia> x = reshape(1:16, 4, 4)
4×4 reshape(::UnitRange{Int64}, 4, 4) with eltype Int64:
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
julia> x[2:3, 2:end-1]
2×2 Array{Int64,2}:
6 10
7 11
julia> x[1, [2 3; 4 1]]
2×2 Array{Int64,2}:
5 9
13 1
Присваивание
Общий синтаксис присваивания значений в n-мерном массиве A:
A[I_1, I_2, ..., I_n] = X
где каждый I_k может быть целочисленным скаляром, массивом целых чисел или любым другим поддерживаемым индексом. Это включает Colon (:) для выбора всех индексов в данной размерности, диапазоны вида a:c или a:b:c для выбора смежных или с шагом подсекций, и массивы булевых значений для выбора элементов по своим true индексам.
Если все индексы I_k являются целыми числами, то значение в позиции I_1, I_2, ..., I_n массива A перезаписывается значением X, при необходимости выполняя convert к eltype массива A.
Если любой индекс I_k выбирает более одной позиции, то правая часть X должна быть массивом с такой же формой, как результат индексирования A[I_1, I_2, ..., I_n], или вектором с тем же числом элементов. Значение в позиции I_1[i_1], I_2[i_2], ..., I_n[i_n] массива A перезаписывается значением X[I_1, I_2, ..., I_n], при необходимости выполняя преобразование. Оператор присваивания по элементам .= может использоваться для распространения X на выбранные позиции:
A[I_1, I_2, ..., I_n] .= X
Как и в индексировании, ключевое слово end может использоваться для представления последнего индекса каждой размерности в скобках индексирования, определяемых размером присваиваемого массива. Синтаксис индексированного присваивания без ключевого слова end эквивалентен вызову setindex!:
setindex!(A, X, I_1, I_2, ..., I_n)
Пример:
julia> x = collect(reshape(1:9, 3, 3))
3×3 Array{Int64,2}:
1 4 7
2 5 8
3 6 9
julia> x[3, 3] = -9;
julia> x[1:2, 1:2] = [-1 -4; -2 -5];
julia> x
3×3 Array{Int64,2}:
-1 -4 7
-2 -5 8
3 6 -9
Поддерживаемые типы индексов
В выражении A[I_1, I_2, ..., I_n], каждый I_k может быть скалярным индексом, массивом скалярных индексов или объектом, представляющим массив скалярных индексов и который можно преобразовать в таковой с помощью to_indices:
- Скалярный индекс. По умолчанию это включает:
- Целые числа, не являющиеся булевыми
-
CartesianIndex{N}, которые ведут себя какN-кортеж целых чисел, охватывающий несколько измерений (подробнее см. ниже)
- Массив скалярных индексов. Это включает:
- Векторы и многомерные массивы целых чисел
- Пустые массивы, такие как
[], которые не выбирают ни одного элемента - Диапазоны, такие как
a:cилиa:b:c, которые выбирают смежные или с шагом подсекции отaдоc(включительно) - Любой пользовательский массив скалярных индексов, являющийся подтипом
AbstractArray - Массивы
CartesianIndex{N}(подробнее см. ниже)
- Объект, представляющий массив скалярных индексов и который может быть преобразован в таковой с помощью
to_indices. По умолчанию это включает:-
Colon()(:), который представляет все индексы в данной размерности или во всём массиве - Массивы булевых значений, которые выбирают элементы по своим
trueиндексам (подробнее см. ниже)
-
Некоторые примеры:
julia> A = reshape(collect(1:2:18), (3, 3))
3×3 Array{Int64,2}:
1 7 13
3 9 15
5 11 17
julia> A[4]
7
julia> A[[2, 5, 8]]
3-element Array{Int64,1}:
3
9
15
julia> A[[1 4; 3 8]]
2×2 Array{Int64,2}:
1 7
5 15
julia> A[[]]
0-element Array{Int64,1}
julia> A[1:2:5]
3-element Array{Int64,1}:
1
5
9
julia> A[2, :]
3-element Array{Int64,1}:
3
9
15
julia> A[:, 3]
3-element Array{Int64,1}:
13
15
17
Декартовы индексы
Специальный объект CartesianIndex{N} представляет скалярный индекс, который ведёт себя как N-кортеж целых чисел, охватывающий несколько размерностей. Например:
julia> A = reshape(1:32, 4, 4, 2); julia> A[3, 2, 1] 7 julia> A[CartesianIndex(3, 2, 1)] == A[3, 2, 1] == 7 true
Рассматривая это изолированно, это может показаться относительно тривиальным; CartesianIndex просто собирает несколько целых чисел вместе в один объект, который представляет собой один многомерный индекс. Однако, в сочетании с другими формами индексирования и итераторами, которые возвращают CartesianIndexы, это может привести к очень элегантному и эффективному коду. См. Итерацию ниже, а для некоторых более сложных примеров см. эту запись блога о многомерных алгоритмах и итерации.
Также поддерживаются массивы CartesianIndex{N}. Они представляют собой набор скалярных индексов, каждый из которых охватывает N измерений, что позволяет выполнять индексирование, иногда называемое точечным индексированием. Например, это позволяет получить доступ к диагональным элементам из первой «страницы» A из вышеприведенного примера:
julia> page = A[:,:,1]
4×4 Array{Int64,2}:
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
julia> page[[CartesianIndex(1,1),
CartesianIndex(2,2),
CartesianIndex(3,3),
CartesianIndex(4,4)]]
4-element Array{Int64,1}:
1
6
11
16
Это можно выразить намного проще с помощью векторного умножения и объединения его с обычным целочисленным индексом (вместо выделения первой page из A в качестве отдельного шага). Его даже можно объединить с : для одновременного извлечения обеих диагоналей с двух страниц:
julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), 1]
4-element Array{Int64,1}:
1
6
11
16
julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), :]
4×2 Array{Int64,2}:
1 17
6 22
11 27
16 32
CartesianIndex и массивы CartesianIndex несовместимы с ключевым словом end для представления последнего индекса измерения. Не используйте end в выражениях индексирования, которые могут содержать CartesianIndex или массивы из них.
Логическое индексирование
Часто называемое логическим индексированием или индексированием с логической маской, индексирование булевым массивом выбирает элементы в тех индексах, где его значения true. Индексирование булевым вектором B по сути то же самое, что индексирование вектором целых чисел, возвращаемым findall(B). Аналогично, индексирование N-мерным булевым массивом по сути то же самое, что индексирование вектором CartesianIndex{N}ов, где его значения true. Логический индекс должен быть вектором той же длины, что и измерение, в которое он индексирует, или он должен быть единственным предоставленным индексом и соответствовать размеру и размерности массива, в который он индексирует. Обычно более эффективно использовать булевы массивы в качестве индексов напрямую, вместо того, чтобы сначала вызывать findall.
julia> x = reshape(1:16, 4, 4)
4×4 reshape(::UnitRange{Int64}, 4, 4) with eltype Int64:
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
julia> x[[false, true, true, false], :]
2×4 Array{Int64,2}:
2 6 10 14
3 7 11 15
julia> mask = map(ispow2, x)
4×4 Array{Bool,2}:
1 0 0 0
1 0 0 0
0 0 0 0
1 1 0 1
julia> x[mask]
5-element Array{Int64,1}:
1
2
4
8
16
Количество индексов
Декартово индексирование
Обычный способ индексирования в N-мерный массив — использование ровно N индексов; каждый индекс выбирает положение(я) в своем конкретном измерении. Например, в трехмерном массиве A = rand(4, 3, 2), A[2, 3, 1] выберет число во второй строке третьего столбца в первой «странице» массива. Это часто называется декартовым индексированием.
Линейное индексирование
Когда предоставлен ровно один индекс i, этот индекс больше не представляет положение в конкретном измерении массива. Вместо этого он выбирает i-й элемент, используя порядок обхода по столбцам, который линейно охватывает весь массив. Это известно как линейное индексирование. По сути, это рассматривает массив так, как будто он был преобразован в одномерный вектор с помощью vec.
julia> A = [2 6; 4 7; 3 1]
3×2 Array{Int64,2}:
2 6
4 7
3 1
julia> A[5]
7
julia> vec(A)[5]
7
Линейный индекс в массив A может быть преобразован в CartesianIndex для декартова индексирования с CartesianIndices(A)[i] (см. CartesianIndices), а набор N декартовых индексов может быть преобразован в линейный индекс с LinearIndices(A)[i_1, i_2, ..., i_N] (см. LinearIndices).
julia> CartesianIndices(A)[5] CartesianIndex(2, 2) julia> LinearIndices(A)[2, 2] 5
Важно отметить, что существует очень большая асимметрия в производительности этих преобразований. Преобразование линейного индекса в набор декартовых индексов требует деления и взятия остатка, тогда как преобразование в обратную сторону — это просто умножения и сложения. В современных процессорах целочисленное деление может быть в 10-50 раз медленнее, чем умножение. Хотя некоторые массивы — например, Array сам — реализуются с помощью линейного блока памяти и напрямую используют линейный индекс в своих реализациях, другие массивы — например, Diagonal — нуждаются в полном наборе декартовых индексов для выполнения поиска (см. IndexStyle, чтобы узнать, какой из них какой). Таким образом, при итерации по всему массиву гораздо лучше использовать итерацию по eachindex(A), а не 1:length(A). Это не только будет намного быстрее в случаях, когда A IndexCartesian, но также будет поддерживать OffsetArrays.
Опущенные и дополнительные индексы
Помимо линейного индексирования, N-мерный массив может быть проиндексирован с меньшим или большим количеством чем N индексов в определенных ситуациях.
Индексы могут быть опущены, если все хвостовые измерения, которые не индексируются, имеют длину один. Другими словами, хвостовые индексы могут быть опущены только в том случае, если существует только одно возможное значение, которое эти опущенные индексы могли бы принимать для выражения индексирования внутри границ. Например, четырехмерный массив с размером (3, 4, 2, 1) может быть индексирован только тремя индексами, так как измерение, которое пропускается (четвертое измерение), имеет длину один. Обратите внимание, что линейное индексирование имеет приоритет перед этим правилом.
julia> A = reshape(1:24, 3, 4, 2, 1)
3×4×2×1 reshape(::UnitRange{Int64}, 3, 4, 2, 1) with eltype Int64:
[:, :, 1, 1] =
1 4 7 10
2 5 8 11
3 6 9 12
[:, :, 2, 1] =
13 16 19 22
14 17 20 23
15 18 21 24
julia> A[1, 3, 2] # Omits the fourth dimension (length 1)
19
julia> A[1, 3] # Attempts to omit dimensions 3 & 4 (lengths 2 and 1)
ERROR: BoundsError: attempt to access 3×4×2×1 reshape(::UnitRange{Int64}, 3, 4, 2, 1) with eltype Int64 at index [1, 3]
julia> A[19] # Linear indexing
19
При опущении всех индексов с A[], эта семантика предоставляет простой способ извлечения единственного элемента в массиве и одновременной гарантии, что в массиве был только один элемент.
Аналогично, может быть предоставлено больше чем N индексов, если все индексы за пределами размерности массива 1 (или, более общо, являются первым и единственным элементом axes(A, d) где d — это номер конкретного измерения). Это позволяет индексировать векторы как матрицы с одним столбцом, например:
julia> A = [8,6,7]
3-element Array{Int64,1}:
8
6
7
julia> A[2,1]
6
Итерация
Рекомендуемые способы итерации по всему массиву:
for a in A
# Do something with the element a
end
for i in eachindex(A)
# Do something with i and/or A[i]
end
Первый конструкт используется, когда вам нужно значение, но не индекс, каждого элемента. Во втором конструкте, i будет Int если A — это тип массива с быстрым линейным индексированием; в противном случае он будет CartesianIndex.
julia> A = rand(4,3);
julia> B = view(A, 1:3, 2:3);
julia> for i in eachindex(B)
@show i
end
i = CartesianIndex(1, 1)
i = CartesianIndex(2, 1)
i = CartesianIndex(3, 1)
i = CartesianIndex(1, 2)
i = CartesianIndex(2, 2)
i = CartesianIndex(3, 2)
В отличие от for i = 1:length(A), итерация с помощью eachindex предоставляет эффективный способ итерации по любому типу массива.
Свойства массивов
Если вы создаёте пользовательский тип AbstractArray, вы можете указать, что он имеет быстрое линейное индексирование, используя
Base.IndexStyle(::Type{<:MyArray}) = IndexLinear()
Эта настройка заставит eachindex итерацию по MyArray использовать целые числа. Если вы не укажете это свойство, будет использоваться значение по умолчанию IndexCartesian().
Массивы и векторизованные операторы и функции
Для массивов поддерживаются следующие операторы:
- Унарные арифметические —
-,+ - Бинарные арифметические —
-,+,*,/,\,^ - Сравнения —
==,!=,≈(isapprox),≉
Для удобного векторизации математических и других операций Julia предоставляет точку синтаксиса f.(args...), например sin.(x) или min.(x,y), для поэлементных операций над массивами или смесями массивов и скаляров (операция расширения); эти операции имеют дополнительное преимущество «слияния» в один цикл при объединении с другими вызовами точек, например, sin.(cos.(x)).
Кроме того, каждый бинарный оператор поддерживает точковую версию, которая может быть применена к массивам (и комбинациям массивов и скаляров) в таких слитых операциях расширения, например, z .== sin.(x .* y).
Обратите внимание, что сравнения, такие как ==, работают над целыми массивами, давая один булев ответ. Используйте операторы точек, такие как .== для поэлементных сравнений. (Для операций сравнения, таких как <, только поэлементная .< версия применима к массивам.)
Также обратите внимание на различие между max.(a,b), который broadcast max поэлементно над a и b, и maximum(a), который находит наибольшее значение в a. Такое же соотношение справедливо для min.(a,b) и minimum(a).
Расширение
Иногда полезно выполнять поэлементные бинарные операции над массивами разного размера, например, добавлять вектор к каждому столбцу матрицы. Неэффективный способ сделать это — дублировать вектор до размера матрицы:
julia> a = rand(2,1); A = rand(2,3);
julia> repeat(a,1,3)+A
2×3 Array{Float64,2}:
1.20813 1.82068 1.25387
1.56851 1.86401 1.67846
Это расточительно, когда размеры становятся большими, поэтому Julia предоставляет broadcast, который расширяет одноэлементные измерения аргументов массива для соответствия соответствующему измерению в другом массиве без использования дополнительной памяти и применяет заданную функцию поэлементно:
julia> broadcast(+, a, A)
2×3 Array{Float64,2}:
1.20813 1.82068 1.25387
1.56851 1.86401 1.67846
julia> b = rand(1,2)
1×2 Array{Float64,2}:
0.867535 0.00457906
julia> broadcast(+, a, b)
2×2 Array{Float64,2}:
1.71056 0.847604
1.73659 0.873631
Операторы с точкой такие как .+ и .* эквивалентны вызовам broadcast (за исключением того, что они объединяются, как описано ниже). Также есть функция broadcast! для указания явного места назначения (к которому также можно получить доступ с помощью объединения при помощи .= присваивания). Фактически, f.(args...) эквивалентно broadcast(f, args...), обеспечивая удобный синтаксис для трансляции любой функции (синтаксис точек). Вложенные "вызовы с точкой" f.(...) (включая вызовы .+ и так далее) автоматически объединяются в один вызов broadcast.
Кроме того, broadcast не ограничивается массивами (см. документацию функции), она также обрабатывает кортежи и рассматривает любой аргумент, который не является массивом, кортежем или Ref (за исключением Ptr) как «скаляр».
julia> convert.(Float32, [1, 2])
2-element Array{Float32,1}:
1.0
2.0
julia> ceil.((UInt8,), [1.2 3.4; 5.6 6.7])
2×2 Array{UInt8,2}:
0x02 0x04
0x06 0x07
julia> string.(1:3, ". ", ["First", "Second", "Third"])
3-element Array{String,1}:
"1. First"
"2. Second"
"3. Third"
Реализация
Базовый тип массива в Julia — это абстрактный тип AbstractArray{T,N}. Он параметризуется количеством измерений N и типом элементов T. AbstractVector и AbstractMatrix являются псевдонимами для случаев 1-d и 2-d соответственно. Операции над объектами AbstractArray определяются с помощью операторов и функций более высокого уровня, независимо от базового хранилища. Эти операции обычно работают корректно как резервный вариант для любой конкретной реализации массива.
Тип AbstractArray включает в себя все, что хоть как-то похоже на массив, и реализации его могут сильно отличаться от обычных массивов. Например, элементы могут вычисляться по запросу, а не храниться. Однако любой конкретный тип AbstractArray{T,N} должен, как правило, реализовывать как минимум size(A) (возвращающий кортеж Int), getindex(A,i) и getindex(A,i1,...,iN); изменяемые массивы также должны реализовывать setindex!. Рекомендуется, чтобы эти операции имели практически постоянную временную сложность или, технически, сложность Õ(1), иначе некоторые функции массивов могут быть неожиданно медленными. Конкретные типы также обычно должны предоставлять метод similar(A,T=eltype(A),dims=size(A)), который используется для выделения аналогичного массива для copy и других операций вне места. Независимо от того, как AbstractArray{T,N} представлен внутри, T является типом объекта, возвращаемого индексированием с использованием целых чисел (A[1, ..., 1], когда A не пустой) и N должно быть длиной кортежа, возвращаемого size. Более подробную информацию о создании пользовательских реализаций AbstractArray см. в руководстве по интерфейсу массивов в главе по интерфейсам.
DenseArray — это абстрактный подтип AbstractArray, предназначенный для включения всех массивов, где элементы хранятся непрерывно в порядке следования столбцов (см. дополнительные заметки в Рекомендациях по производительности). Тип Array — это конкретный экземпляр DenseArray; Vector и Matrix являются псевдонимами для случаев 1-d и 2-d соответственно. Очень мало операций реализованы специально для Array помимо тех, которые требуются для всех AbstractArray; большая часть библиотеки массивов реализована обобщенным способом, что позволяет всем пользовательским массивам вести себя аналогично.
SubArray — это специализация AbstractArray, которая выполняет индексирование путем совместного использования памяти с исходным массивом, а не путем копирования. SubArray создается с помощью функции view, которая вызывается так же, как getindex (с массивом и рядом аргументов индекса). Результат view выглядит так же, как результат getindex, за исключением того, что данные остаются на месте. view хранит входные векторы индексов в объекте SubArray, который позже может использоваться для непрямого индексирования исходного массива. Разместив макрос @views перед выражением или блоком кода, любой срез array[...] в этом выражении будет преобразован для создания представления SubArray.
BitArray — это экономичные «упакованные» булевы массивы, которые хранят один бит на булево значение. Они могут использоваться аналогично массивам Array{Bool} (которые хранят один байт на булево значение), и могут быть преобразованы в/из последних с помощью Array(bitarray) и BitArray(array) соответственно.
«Массив с шагами» хранится в памяти с элементами, расположенными с регулярными смещениями, так что экземпляр с поддерживаемым типом элементов isbits может передаваться внешним функциям C и Fortran, которые ожидают такой порядок памяти. Массивы с шагами должны определять метод strides(A), который возвращает кортеж «шагов» для каждого измерения; предоставленный метод stride(A,k) обращается к элементу k в этом кортеже. Увеличение индекса измерения k на 1 должно увеличивать индекс i getindex(A,i) на stride(A,k). Если метод преобразования указателя Base.unsafe_convert(Ptr{T}, A) предоставляется, порядок памяти должен соответствовать этим шагам аналогичным образом. DenseArray — это очень конкретный пример массива с шагами, где элементы расположены непрерывно, поэтому он предоставляет своим подтипам соответствующее определение strides. Более конкретные примеры можно найти в руководстве по интерфейсу массивов с шагами. StridedVector и StridedMatrix — это удобные псевдонимы для многих встроенных типов массивов, которые считаются массивами с шагами, позволяя им отправлять запросы на выбор специализированных реализаций, которые вызывают высоконастроенные и оптимизированные функции BLAS и LAPACK, используя только указатель и шаги.
Следующий пример вычисляет QR-разложение небольшой части большего массива, не создавая временных объектов и вызывая соответствующую функцию LAPACK с правильными размерами ведущего измерения и параметрами шага.
julia> a = rand(10, 10)
10×10 Array{Float64,2}:
0.517515 0.0348206 0.749042 0.0979679 … 0.75984 0.950481 0.579513
0.901092 0.873479 0.134533 0.0697848 0.0586695 0.193254 0.726898
0.976808 0.0901881 0.208332 0.920358 0.288535 0.705941 0.337137
0.657127 0.0317896 0.772837 0.534457 0.0966037 0.700694 0.675999
0.471777 0.144969 0.0718405 0.0827916 0.527233 0.173132 0.694304
0.160872 0.455168 0.489254 0.827851 … 0.62226 0.0995456 0.946522
0.291857 0.769492 0.68043 0.629461 0.727558 0.910796 0.834837
0.775774 0.700731 0.700177 0.0126213 0.00822304 0.327502 0.955181
0.9715 0.64354 0.848441 0.241474 0.591611 0.792573 0.194357
0.646596 0.575456 0.0995212 0.038517 0.709233 0.477657 0.0507231
julia> b = view(a, 2:2:8,2:2:4)
4×2 view(::Array{Float64,2}, 2:2:8, 2:2:4) with eltype Float64:
0.873479 0.0697848
0.0317896 0.534457
0.455168 0.827851
0.700731 0.0126213
julia> (q, r) = qr(b);
julia> q
4×4 LinearAlgebra.QRCompactWYQ{Float64,Array{Float64,2}}:
-0.722358 0.227524 -0.247784 -0.604181
-0.0262896 -0.575919 -0.804227 0.144377
-0.376419 -0.75072 0.540177 -0.0541979
-0.579497 0.230151 -0.00552346 0.781782
julia> r
2×2 Array{Float64,2}:
-1.20921 -0.383393
0.0 -0.910506
© 2009–2019 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.2.0/manual/arrays/