Многомерные массивы
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}:
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
Количество индексов
Декартово индексирование
Обычный способ индексирования в 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 и 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
© 2009–2019 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.1.1/manual/arrays/