Многомерные массивы
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 единичная матрица (требуется using LinearAlgebra) |
range(start, stop=stop, length=n) |
диапазон n линейно распределенных элементов от start до stop
|
fill!(A, x) |
заполнение массива A значением x
|
fill(x, dims...) |
массив Array заполненный значением x
|
Рассмотрим следующие примеры, чтобы увидеть различные способы передачи размеров в эти функции:
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, а первый аргумент — тип элемента — необязателен, по умолчанию Float64.
Массивы-литералы
Массивы также могут быть напрямую построены с помощью квадратных скобок; синтаксис [A, B, C, ...] создаёт одномерный массив (т.е. вектор), содержащий аргументы, разделённые запятыми, в качестве своих элементов. Тип элемента (eltype) результирующего массива автоматически определяется типами аргументов в фигурных скобках. Если все аргументы одного типа, то это его eltype. Если у них есть общий тип повышения, то они преобразуются в этот тип с помощью convert, и этот тип является типом массива eltype. В противном случае создаётся разнородный массив, который может содержать что угодно — Vector{Any}; сюда входит и литерал [], где аргументов нет.
julia> [1,2,3] # An array of `Int`s
3-element Array{Int64,1}:
1
2
3
julia> promote(1, 2.3, 4//5) # This combination of Int, Float64 and Rational promotes to Float64
(1.0, 2.3, 0.8)
julia> [1, 2.3, 4//5] # Thus that's the element type of this Array
3-element Array{Float64,1}:
1.0
2.3
0.8
julia> []
0-element Array{Any,1}
Конкатенация
Если аргументы в квадратных скобках разделены точкой с запятой (;) или новой строкой вместо запятой, то их содержимое вертикально конкатенируется, а не сами аргументы используются в качестве элементов.
julia> [1:2, 4:5] # Has a comma, so no concatenation occurs. The ranges are themselves the elements
2-element Array{UnitRange{Int64},1}:
1:2
4:5
julia> [1:2; 4:5]
4-element Array{Int64,1}:
1
2
4
5
julia> [1:2; 4:5]
4-element Array{Int64,1}:
1
2
4
5
julia> [1:2
4:5
6]
5-element Array{Int64,1}:
1
2
4
5
6
Аналогично, если аргументы разделены табуляцией или пробелами, их содержимое горизонтально конкатенируется.
julia> [1:2 4:5 7:8]
2×3 Array{Int64,2}:
1 4 7
2 5 8
julia> [[1,2] [4,5] [7,8]]
2×3 Array{Int64,2}:
1 4 7
2 5 8
julia> [1 2 3] # Numbers can also be horizontally concatenated
1×3 Array{Int64,2}:
1 2 3
Использование точек с запятой (или новых строк) и пробелов (или табуляции) можно комбинировать для конкатенации как по горизонтали, так и по вертикали одновременно.
julia> [1 2
3 4]
2×2 Array{Int64,2}:
1 2
3 4
julia> [zeros(Int, 2, 2) [1; 2]
[3 4] 5]
3×3 Array{Int64,2}:
0 0 1
0 0 2
3 4 5
В более общем случае конкатенацию можно выполнить с помощью функции cat. Эти синтаксические конструкции являются сокращениями для вызовов функций, которые сами являются удобными функциями:
| Синтаксис | Функция | Описание |
|---|---|---|
cat |
конкатенация входных массивов по размерности(ям) k
|
|
[A; B; C; ...] |
vcat |
сокращение для `cat(A...; dims=1) |
[A B C ...] |
hcat |
сокращение для `cat(A...; dims=2) |
[A B; C D; ...] |
hvcat |
одновременная вертикальная и горизонтальная конкатенация |
Массивы с типом элементов
Массив с определенным типом элементов может быть создан с использованием синтаксиса 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 не ограничен массивами (см. документацию функции); он также обрабатывает скаляры, кортежи и другие коллекции. По умолчанию только некоторые типы аргументов рассматриваются как скаляры, включая (но не ограничиваясь) Numberы, Stringы, Symbolы, Typeы, Functionы и некоторые общие одиночные элементы, такие как missing и nothing. Все остальные аргументы обрабатываются итеративно или по индексам поэлементно.
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 и 2 измерениями. Операции над объектами 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 и 2 измерениями. Очень мало операций реализуется специально для 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
- 1iid, независимо и одинаково распределённые.
© 2009–2020 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.4.2/manual/arrays/