Многомерные массивы
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, 2)
2×2 Array{Int8,2}:
0 0
0 0
julia> zeros(Int8, (2, 2))
2×2 Array{Int8,2}:
0 0
0 0
julia> zeros((2, 2))
2×2 Array{Float64,2}:
0.0 0.0
0.0 0.0
Здесь, (2, 2) является 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 — это массив с тем же количеством измерений, что и сумма размерностей всех индексов.
Если все индексы являются векторами, например, то форма 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}]]. Все измерения, индексированные скалярами, удаляются. Например, результатом A[2, I, 3] является массив размером size(I). Его i-й элемент заполняется A[2, I[i], 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:n-1 иногда используются для обозначения меж-индексного расположения между n-1 и n. Например, функция searchsorted использует эту конвенцию для обозначения точки вставки значения, отсутствующего в отсортированном массиве:
julia> a = [1,2,5,6,7]; julia> searchsorted(a, 3) 3:2
Присваивание
Общая синтаксическая конструкция для присваивания значений в n-мерном массиве A:
A[I_1, I_2, ..., I_n] = X
где каждый I_k может быть скалярным целым числом, массивом целых чисел или любым другим поддерживаемым индексом. Это включает Colon (:) для выбора всех индексов в данном измерении, диапазоны вида a:c или a:b:c для выбора смежных или с шагом подмножеств, и массивы булевых значений для выбора элементов по их true индексам.
Если X является массивом, он должен иметь такое же количество элементов, как произведение длин индексов: prod(length(I_1), length(I_2), ..., length(I_n)). Значение в местоположении I_1[i_1], I_2[i_2], ..., I_n[i_n] из A перезаписывается значением X[i_1, i_2, ..., i_n]. Если X не является массивом, его значение записывается во все ссылающиеся местоположения A.
Как и в Индексировании, ключевое слово 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}:
true false false false
true false false false
false false false false
true true false true
julia> x[mask]
5-element Array{Int64,1}:
1
2
4
8
16
Итерация
Рекомендуемые способы итерации по всему массиву:
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),≉
Большинство перечисленных выше бинарных арифметических операторов также работают поэлементно, когда один из аргументов является скалярным: -, +, и * при скалярном аргументе, и / и \ когда знаменатель является скалярным. Например, [1, 2] + 3 == [4, 5] и [6, 4] / 2 == [3, 2].
Кроме того, для обеспечения удобной векторизации математических и других операций 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-мерного и 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 сверх тех, которые требуются для всех AbstractArrays ; большая часть библиотеки массивов реализована обобщенным способом, позволяющим всем пользовательским массивам вести себя аналогично.
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/v0.7.0/manual/arrays/