Single- and multi-dimensional Arrays
Джулия, как и большинство языков технического программирования, предоставляет реализацию массивов первого класса. Большинство языков технического программирования уделяют много внимания своей реализации массивов в ущерб другим контейнерам. Джулия не рассматривает массивы каким-либо особым образом. Библиотека массивов реализована почти полностью на самой Джулии и получает свою производительность от компилятора, как и любой другой код, написанный на Джулии. Таким образом, также возможно определить пользовательские типы массивов, унаследовав от AbstractArray
. См. manual section on the AbstractArray interface для получения дополнительной информации о реализации пользовательского типа массива.
Массив — это коллекция объектов, хранящихся в многомерной сетке. Разрешены нульмерные массивы, см. this FAQ entry. В самом общем случае массив может содержать объекты типа Any
. Для большинства вычислительных задач массивы должны содержать объекты более специфического типа, такие как Float64
или Int32
.
В общем, в отличие от многих других языков технического программирования, Julia не требует, чтобы программы были написаны в векторизованном стиле для достижения производительности. Компилятор Julia использует вывод типов и генерирует оптимизированный код для скалярной индексации массивов, позволяя писать программы в удобном и читаемом стиле, не жертвуя производительностью и иногда используя меньше памяти.
In Julia, all arguments to functions are passed by sharing (i.e. by pointers). Some technical computing languages pass arrays by value, and while this prevents accidental modification by callees of a value in the caller, it makes avoiding unwanted copying of arrays difficult. By convention, a function name ending with a !
indicates that it will mutate or destroy the value of one or more of its arguments (compare, for example, sort
and sort!
). Callees must make explicit copies to ensure that they don't modify inputs that they don't intend to change. Many non-mutating functions are implemented by calling a function of the same name with an added !
at the end on an explicit copy of the input, and returning that copy.
Basic Functions
Function | Description |
---|---|
eltype(A) | the type of the elements contained in A |
length(A) | the number of elements in A |
ndims(A) | the number of dimensions of A |
size(A) | a tuple containing the dimensions of A |
size(A,n) | the size of A along dimension n |
axes(A) | a tuple containing the valid indices of A |
axes(A,n) | a range expressing the valid indices along dimension n |
eachindex(A) | an efficient iterator for visiting each position in A |
stride(A,k) | the stride (linear index distance between adjacent elements) along dimension k |
strides(A) | a tuple of the strides in each dimension |
Construction and Initialization
Предоставлено множество функций для создания и инициализации массивов. В следующем списке таких функций вызовы с аргументом dims...
могут принимать либо один кортеж размеров размерностей, либо серию размеров размерностей, переданных в виде переменного числа аргументов. Большинство из этих функций также принимают первый входной параметр T
, который является типом элементов массива. Если тип T
опущен, он по умолчанию будет равен Float64
.
Function | Description |
---|---|
Array{T}(undef, dims...) | an uninitialized dense Array |
zeros(T, dims...) | an Array of all zeros |
ones(T, dims...) | an Array of all ones |
trues(dims...) | a BitArray with all values true |
falses(dims...) | a BitArray with all values false |
reshape(A, dims...) | an array containing the same data as A , but with different dimensions |
copy(A) | copy A |
deepcopy(A) | copy A , recursively copying its elements |
similar(A, T, dims...) | an uninitialized array of the same type as A (dense, sparse, etc.), but with the specified element type and dimensions. The second and third arguments are both optional, defaulting to the element type and dimensions of A if omitted. |
reinterpret(T, A) | an array with the same binary data as A , but with element type T |
rand(T, dims...) | an Array with random, iid [1] and uniformly distributed values. For floating point types T , the values lie in the half-open interval $[0, 1)$. |
randn(T, dims...) | an Array with random, iid and standard normally distributed values |
Matrix{T}(I, m, n) | m -by-n identity matrix. Requires using LinearAlgebra for I . |
range(start, stop, n) | a range of n linearly spaced elements from start to stop |
fill!(A, x) | fill the array A with the value x |
fill(x, dims...) | an Array filled with the value x . In particular, fill(x) constructs a zero-dimensional Array containing x . |
Чтобы увидеть различные способы передачи размеров этим функциям, рассмотрим следующие примеры:
julia> zeros(Int8, 2, 3)
2×3 Matrix{Int8}:
0 0 0
0 0 0
julia> zeros(Int8, (2, 3))
2×3 Matrix{Int8}:
0 0 0
0 0 0
julia> zeros((2, 3))
2×3 Matrix{Float64}:
0.0 0.0 0.0
0.0 0.0 0.0
Здесь (2, 3)
является Tuple
, а первый аргумент — тип элемента — является необязательным и по умолчанию равен Float64
.
Array literals
Массивы также могут быть непосредственно созданы с помощью квадратных скобок; синтаксис [A, B, C, ...]
создает одномерный массив (т.е. вектор), содержащий аргументы, разделенные запятыми, в качестве своих элементов. Тип элемента (eltype
) результирующего массива автоматически определяется типами аргументов внутри скобок. Если все аргументы одного типа, то это его eltype
. Если у них есть общий promotion type, то они преобразуются в этот тип с использованием convert
, и этот тип является eltype
массива. В противном случае создается гетерогенный массив, который может содержать что угодно — Vector{Any}
— это включает литерал []
, когда аргументы не указаны. Array literal can be typed с синтаксисом T[A, B, C, ...]
, где T
— это тип.
julia> [1, 2, 3] # An array of `Int`s
3-element Vector{Int64}:
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 Vector{Float64}:
1.0
2.3
0.8
julia> Float32[1, 2.3, 4//5] # Specify element type manually
3-element Vector{Float32}:
1.0
2.3
0.8
julia> []
Any[]
Concatenation
Если аргументы внутри квадратных скобок разделены одиночными точками с запятой (;
) или переносами строк вместо запятых, то их содержимое вертикально конкатенируется вместе, а не используются как элементы сами по себе.
julia> [1:2, 4:5] # Has a comma, so no concatenation occurs. The ranges are themselves the elements
2-element Vector{UnitRange{Int64}}:
1:2
4:5
julia> [1:2; 4:5]
4-element Vector{Int64}:
1
2
4
5
julia> [1:2
4:5
6]
5-element Vector{Int64}:
1
2
4
5
6
Аналогично, если аргументы разделены табуляцией, пробелами или двойными точками с запятой, то их содержимое горизонтально конкатенируется вместе.
julia> [1:2 4:5 7:8]
2×3 Matrix{Int64}:
1 4 7
2 5 8
julia> [[1,2] [4,5] [7,8]]
2×3 Matrix{Int64}:
1 4 7
2 5 8
julia> [1 2 3] # Numbers can also be horizontally concatenated
1×3 Matrix{Int64}:
1 2 3
julia> [1;; 2;; 3;; 4]
1×4 Matrix{Int64}:
1 2 3 4
Одиночные точки с запятой (или новые строки) и пробелы (или табуляции) могут быть объединены для конкатенации как горизонтально, так и вертикально одновременно.
julia> [1 2
3 4]
2×2 Matrix{Int64}:
1 2
3 4
julia> [zeros(Int, 2, 2) [1; 2]
[3 4] 5]
3×3 Matrix{Int64}:
0 0 1
0 0 2
3 4 5
julia> [[1 1]; 2 3; [4 4]]
3×2 Matrix{Int64}:
1 1
2 3
4 4
Пробелы (и табуляции) имеют более высокий приоритет, чем точки с запятой, выполняя любые горизонтальные конкатенации сначала, а затем конкатенируя результат. Использование двойных точек с запятой для горизонтальной конкатенации, с другой стороны, выполняет любые вертикальные конкатенации перед горизонтальной конкатенацией результата.
julia> [zeros(Int, 2, 2) ; [3 4] ;; [1; 2] ; 5]
3×3 Matrix{Int64}:
0 0 1
0 0 2
3 4 5
julia> [1:2; 4;; 1; 3:4]
3×2 Matrix{Int64}:
1 1
2 3
4 4
Точно так же, как ;
и ;;
соединяют в первом и втором измерении, использование большего количества точек с запятой расширяет эту же общую схему. Количество точек с запятой в разделителе указывает на конкретное измерение, поэтому ;;;
соединяет в третьем измерении, ;;;;
в 4-м и так далее. Меньшее количество точек с запятой имеет приоритет, поэтому более низкие измерения обычно соединяются первыми.
julia> [1; 2;; 3; 4;; 5; 6;;;
7; 8;; 9; 10;; 11; 12]
2×3×2 Array{Int64, 3}:
[:, :, 1] =
1 3 5
2 4 6
[:, :, 2] =
7 9 11
8 10 12
Как и прежде, пробелы (и табуляции) для горизонтальной конкатенации имеют более высокий приоритет, чем любое количество точек с запятой. Таким образом, многомерные массивы также могут быть записаны, указывая их строки сначала, с их элементами, текстуально расположенными аналогично их компоновке:
julia> [1 3 5
2 4 6;;;
7 9 11
8 10 12]
2×3×2 Array{Int64, 3}:
[:, :, 1] =
1 3 5
2 4 6
[:, :, 2] =
7 9 11
8 10 12
julia> [1 2;;; 3 4;;;; 5 6;;; 7 8]
1×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
1 2
[:, :, 2, 1] =
3 4
[:, :, 1, 2] =
5 6
[:, :, 2, 2] =
7 8
julia> [[1 2;;; 3 4];;;; [5 6];;; [7 8]]
1×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
1 2
[:, :, 2, 1] =
3 4
[:, :, 1, 2] =
5 6
[:, :, 2, 2] =
7 8
Хотя оба термина означают конкатенацию во втором измерении, пробелы (или табуляции) и ;;
не могут появляться в одном и том же выражении массива, если двойная точка с запятой просто служит символом "продолжения строки". Это позволяет одной горизонтальной конкатенации занимать несколько строк (без того, чтобы разрыв строки интерпретировался как вертикальная конкатенация).
julia> [1 2 ;;
3 4]
1×4 Matrix{Int64}:
1 2 3 4
Терминальные точки с запятой также могут использоваться для добавления завершающих размерностей длины 1.
julia> [1;;]
1×1 Matrix{Int64}:
1
julia> [2; 3;;;]
2×1×1 Array{Int64, 3}:
[:, :, 1] =
2
3
Более общим образом, конкатенацию можно выполнить с помощью функции cat
. Эти синтаксисы являются сокращениями для вызовов функций, которые сами по себе являются удобными функциями:
Syntax | Function | Description |
---|---|---|
cat | concatenate input arrays along dimension(s) k | |
[A; B; C; ...] | vcat | shorthand for cat(A...; dims=1) |
[A B C ...] | hcat | shorthand for cat(A...; dims=2) |
[A B; C D; ...] | hvcat | simultaneous vertical and horizontal concatenation |
[A; C;; B; D;;; ...] | hvncat | simultaneous n-dimensional concatenation, where number of semicolons indicate the dimension to concatenate |
Typed array literals
Массив с определенным типом элементов можно создать, используя синтаксис T[A, B, C, ...]
. Это создаст одномерный массив с типом элемента T
, инициализированный для содержимого элементов A
, B
, C
и т.д. Например, Any[x, y, z]
создает гетерогенный массив, который может содержать любые значения.
Синтаксис конкатенации также может быть предваренен типом для указания типа элемента результата.
julia> [[1 2] [3 4]]
1×4 Matrix{Int64}:
1 2 3 4
julia> Int8[[1 2] [3 4]]
1×4 Matrix{Int8}:
1 2 3 4
Comprehensions
Понимания предоставляют общий и мощный способ создания массивов. Синтаксис понимания похож на нотацию построения множеств в математике:
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,...)
возвращает скаляр.
Следующий пример вычисляет взвешенное среднее текущего элемента и его левого и правого соседей вдоль 1-д сетки. :
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
Результирующий тип массива зависит от типов вычисляемых элементов, так же как и array literals. Чтобы явно контролировать тип, к пониманию можно добавить тип. Например, мы могли бы запросить результат в одинарной точности, написав:
Float32[ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]
Generator Expressions
Компрехеншены также могут быть записаны без заключительных квадратных скобок, создавая объект, известный как генератор. Этот объект может быть итерирован для получения значений по мере необходимости, вместо того чтобы выделять массив и хранить их заранее (см. Iteration). Например, следующее выражение суммирует ряд без выделения памяти:
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 Matrix{Tuple{Float64, Int64}}:
(0.5, 1) (0.333333, 3)
(0.333333, 2) (0.25, 4)
Генераторы реализуются через внутренние функции. Точно так же, как внутренние функции, используемые в других местах языка, переменные из окружающей области могут быть "захвачены" во внутренней функции. Например, sum(p[i] - q[i] for i=1:n)
захватывает три переменные p
, q
и n
из окружающей области. Захваченные переменные могут представлять собой проблемы с производительностью; см. performance tips.
Диапазоны в генераторах и выражениях могут зависеть от предыдущих диапазонов, если написать несколько ключевых слов for
:
julia> [(i, j) for i=1:3 for j=1:i]
6-element Vector{Tuple{Int64, Int64}}:
(1, 1)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
(3, 3)
В таких случаях результат всегда 1-д.
Сгенерированные значения можно фильтровать с помощью ключевого слова if
:
julia> [(i, j) for i=1:3 for j=1:i if i+j == 4]
2-element Vector{Tuple{Int64, Int64}}:
(2, 2)
(3, 1)
Indexing
Общая синтаксическая структура для индексации в n-мерном массиве A
выглядит следующим образом:
X = A[I_1, I_2, ..., I_n]
где каждый I_k
может быть скалярным целым числом, массивом целых чисел или любым другим supported index. Это включает 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 Matrix{Int64}:
1 2
1 2
julia> A[[1 2; 1 2], 1, 2, 1]
2×2 Matrix{Int64}:
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 Matrix{Int64}:
6 10
7 11
julia> x[1, [2 3; 4 1]]
2×2 Matrix{Int64}:
5 9
13 1
Indexed Assignment
Общая синтаксис для присвоения значений в n-мерном массиве A
выглядит следующим образом:
A[I_1, I_2, ..., I_n] = X
где каждый I_k
может быть скалярным целым числом, массивом целых чисел или любым другим supported index. Это включает 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]
, при необходимости выполняя преобразование. Оператор присваивания поэлементно .=
может быть использован для broadcast X
по выбранным позициям:
A[I_1, I_2, ..., I_n] .= X
Так же, как в Indexing, ключевое слово end
может использоваться для представления последнего индекса каждого измерения в квадратных скобках индексации, как определяется размером массива, в который происходит присваивание. Синтаксис индексированного присваивания без ключевого слова end
эквивалентен вызову setindex!
:
setindex!(A, X, I_1, I_2, ..., I_n)
Пример:
julia> x = collect(reshape(1:9, 3, 3))
3×3 Matrix{Int64}:
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 Matrix{Int64}:
-1 -4 7
-2 -5 8
3 6 -9
Supported index types
В выражении A[I_1, I_2, ..., I_n]
каждый I_k
может быть скалярным индексом, массивом скалярных индексов или объектом, который представляет массив скалярных индексов и может быть преобразован в такой с помощью to_indices
:
Скалярный индекс. По умолчанию это включает:
- Небулевские целые числа
CartesianIndex{N}
, которые ведут себя какN
-кортеж целых чисел, охватывающий несколько измерений (см. ниже для получения дополнительной информации)
Массив скалярных индексов. Это включает:
- Векторы и многомерные массивы целых чисел
- Пустые массивы, такие как
[]
, которые не выбирают элементы, например,A[[]]
(не путать сA[]
) - Диапазоны, такие как
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 Matrix{Int64}:
1 7 13
3 9 15
5 11 17
julia> A[4]
7
julia> A[[2, 5, 8]]
3-element Vector{Int64}:
3
9
15
julia> A[[1 4; 3 8]]
2×2 Matrix{Int64}:
1 7
5 15
julia> A[[]]
Int64[]
julia> A[1:2:5]
3-element Vector{Int64}:
1
5
9
julia> A[2, :]
3-element Vector{Int64}:
3
9
15
julia> A[:, 3]
3-element Vector{Int64}:
13
15
17
julia> A[:, 3:3]
3×1 Matrix{Int64}:
13
15
17
Cartesian indices
Специальный объект 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
ы, это может привести к очень элегантному и эффективному коду. См. Iteration ниже, а для некоторых более продвинутых примеров см. this blog post on multidimensional algorithms and iteration.
Массивы CartesianIndex{N}
также поддерживаются. Они представляют собой коллекцию скалярных индексов, которые охватывают N
измерений, позволяя использовать форму индексации, которая иногда называется покоординатной индексацией. Например, это позволяет получить доступ к диагональным элементам с первой "страницы" A
сверху:
julia> page = A[:, :, 1]
4×4 Matrix{Int64}:
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 Vector{Int64}:
1
6
11
16
Это можно выразить гораздо проще с помощью dot broadcasting и комбинируя это с обычным целым индексом (вместо того, чтобы извлекать первую page
из A
как отдельный шаг). Это даже можно комбинировать с :
, чтобы извлечь обе диагонали из двух страниц одновременно:
julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), 1]
4-element Vector{Int64}:
1
6
11
16
julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), :]
4×2 Matrix{Int64}:
1 17
6 22
11 27
16 32
CartesianIndex
и массивы CartesianIndex
несовместимы с ключевым словом end
для представления последнего индекса измерения. Не используйте end
в выражениях индексации, которые могут содержать либо CartesianIndex
, либо массивы таковых.
Logical indexing
Часто называемое логическим индексированием или индексированием с помощью логической маски, индексирование по булевому массиву выбирает элементы по индексам, где его значения равны true
. Индексирование по булевому вектору B
фактически то же самое, что и индексирование по вектору целых чисел, который возвращается findall(B)
. Аналогично, индексирование по N
-мерному булевому массиву фактически то же самое, что и индексирование по вектору CartesianIndex{N}
s, где его значения равны true
. Логический индекс должен быть массивом той же формы, что и размерность(и), в которую он индексирует, или он должен быть единственным предоставленным индексом и соответствовать форме одномерного измененного представления массива, в который он индексирует. Обычно более эффективно использовать булевы массивы в качестве индексов напрямую, а не сначала вызывать findall
.
julia> x = reshape(1:12, 2, 3, 2)
2×3×2 reshape(::UnitRange{Int64}, 2, 3, 2) with eltype Int64:
[:, :, 1] =
1 3 5
2 4 6
[:, :, 2] =
7 9 11
8 10 12
julia> x[:, [true false; false true; true false]]
2×3 Matrix{Int64}:
1 5 9
2 6 10
julia> mask = map(ispow2, x)
2×3×2 Array{Bool, 3}:
[:, :, 1] =
1 0 0
1 1 0
[:, :, 2] =
0 0 0
1 0 0
julia> x[mask]
4-element Vector{Int64}:
1
2
4
8
julia> x[vec(mask)] == x[mask] # we can also index with a single Boolean vector
true
Number of indices
Cartesian indexing
Обычный способ индексирования в N
-мерном массиве заключается в использовании ровно N
индексов; каждый индекс выбирает позицию(и) в своем конкретном измерении. Например, в трехмерном массиве A = rand(4, 3, 2)
, A[2, 3, 1]
выберет число во втором ряду третьего столбца на первой "странице" массива. Это часто называется декартовым индексированием.
Linear indexing
Когда предоставлен ровно один индекс i
, этот индекс больше не представляет собой местоположение в определенном измерении массива. Вместо этого он выбирает i
-й элемент, используя порядок итерации по столбцам, который линейно охватывает весь массив. Это известно как линейная индексация. Это по сути рассматривает массив так, как будто он был преобразован в одномерный вектор с vec
.
julia> A = [2 6; 4 7; 3 1]
3×2 Matrix{Int64}:
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. Если нужны только значения, то лучше просто перебрать массив напрямую, т.е. for a in A
.
Omitted and extra indices
В дополнение к линейной индексации, 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 Vector{Int64}:
8
6
7
julia> A[2, 1]
6
Iteration
Рекомендуемые способы перебора всего массива:
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
предоставляет эффективный способ итерации по любому типу массива. Кроме того, это также поддерживает обобщенные массивы с пользовательской индексацией, такие как OffsetArrays.
Array traits
Если вы создадите пользовательский тип AbstractArray
, вы можете указать, что он имеет быстрый линейный индекс, используя
Base.IndexStyle(::Type{<:MyArray}) = IndexLinear()
Эта настройка заставит итерацию eachindex
по MyArray
использовать целые числа. Если вы не укажете эту характеристику, будет использовано значение по умолчанию IndexCartesian()
.
Array and Vectorized Operators and Functions
Поддерживаются следующие операторы для массивов:
- Унарная арифметика –
-
,+
- Двоичная арифметика –
-
,+
,*
,/
,\
,^
- Сравнение –
==
,!=
,≈
(isapprox
),≉
Чтобы обеспечить удобную векторизацию математических и других операций, Julia provides the dot syntax f.(args...)
, например, sin.(x)
или min.(x, y)
, для поэлементных операций над массивами или смесями массивов и скаляров (операция Broadcasting); у них есть дополнительное преимущество "слияния" в один цикл при комбинировании с другими точечными вызовами, например, sin.(cos.(x))
.
Также, каждый бинарный оператор поддерживает dot version, который может быть применен к массивам (и комбинациям массивов и скаляров) в таком fused broadcasting operations, например, z .== sin.(x .* y)
.
Обратите внимание, что такие сравнения, как ==
, работают с целыми массивами, давая единственный логический ответ. Используйте точечные операторы, такие как .==
, для поэлементных сравнений. (Для операций сравнения, таких как <
, только поэлементная версия .<
применима к массивам.)
Also notice the difference between max.(a,b)
, which broadcast
s max
elementwise over a
and b
, and maximum(a)
, which finds the largest value within a
. The same relationship holds for min.(a, b)
and minimum(a)
.
Broadcasting
Иногда полезно выполнять побитовые операции над элементами массивов разного размера, например, добавляя вектор к каждому столбцу матрицы. Неэффективный способ сделать это - скопировать вектор до размера матрицы:
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
Dotted operators, такие как .+
и .*
, эквивалентны вызовам broadcast
(за исключением того, что они объединяются, как described above). Также есть функция broadcast!
, чтобы указать явное назначение (к которому также можно получить доступ в объединенном виде с помощью присваивания .=
). На самом деле, f.(args...)
эквивалентно broadcast(f, args...)
, предоставляя удобный синтаксис для трансляции любой функции (dot syntax). Вложенные "точечные вызовы" f.(...)
(включая вызовы к .+
и т.д.) automatically fuse объединяются в один вызов broadcast
.
Кроме того, broadcast
не ограничивается массивами (см. документацию функции); он также обрабатывает скаляры, кортежи и другие коллекции. По умолчанию только некоторые типы аргументов считаются скалярами, включая (но не ограничиваясь) Number
s, String
s, Symbol
s, Type
s, Function
s и некоторые общие синглетоны, такие как missing
и nothing
. Все остальные аргументы обрабатываются поэлементно или индексируются.
julia> convert.(Float32, [1, 2])
2-element Vector{Float32}:
1.0
2.0
julia> ceil.(UInt8, [1.2 3.4; 5.6 6.7])
2×2 Matrix{UInt8}:
0x02 0x04
0x06 0x07
julia> string.(1:3, ". ", ["First", "Second", "Third"])
3-element Vector{String}:
"1. First"
"2. Second"
"3. Third"
Иногда вам нужен контейнер (например, массив), который обычно участвует в широковещательной передаче, чтобы быть "защищенным" от поведения широковещательной передачи, которое итерирует по всем его элементам. Поместив его внутрь другого контейнера (например, единственного элемента Tuple
), широковещательная передача будет рассматривать его как одно значение.
julia> ([1, 2, 3], [4, 5, 6]) .+ ([1, 2, 3],)
([2, 4, 6], [5, 7, 9])
julia> ([1, 2, 3], [4, 5, 6]) .+ tuple([1, 2, 3])
([2, 4, 6], [5, 7, 9])
Implementation
Базовый тип массива в 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!
. Рекомендуется, чтобы эти операции имели почти постоянную временную сложность, так как в противном случае некоторые функции массивов могут неожиданно работать медленно. Конкретные типы также должны обычно предоставлять метод similar(A, T=eltype(A), dims=size(A))
, который используется для выделения аналогичного массива для copy
и других операций вне места. Независимо от того, как AbstractArray{T,N}
представлен внутренне, T
— это тип объекта, возвращаемого при целочисленном индексировании (A[1, ..., 1]
, когда A
не пустой), а N
должен быть длиной кортежа, возвращаемого size
. Для получения дополнительной информации о том, как определить пользовательские реализации AbstractArray
, см. array interface guide in the interfaces chapter.
DenseArray
является абстрактным подтипом AbstractArray
, предназначенным для включения всех массивов, где элементы хранятся последовательно в порядке столбцов (см. additional notes in Performance Tips). Тип Array
является конкретным экземпляром DenseArray
; Vector
и Matrix
являются псевдонимами для 1-д и 2-д случаев. Очень немногие операции реализованы специально для Array
помимо тех, которые требуются для всех AbstractArray
; большая часть библиотеки массивов реализована в общем виде, что позволяет всем пользовательским массивам вести себя аналогично.
SubArray
является специализацией AbstractArray
, которая выполняет индексацию, разделяя память с оригинальным массивом, а не копируя его. SubArray
создается с помощью функции view
, которая вызывается так же, как getindex
(с массивом и серией аргументов индекса). Результат 4d61726b646f776e2e436f64652822222c2022766965772229_40726566
выглядит так же, как результат 4d61726b646f776e2e436f64652822222c2022676574696e6465782229_40726566
, за исключением того, что данные остаются на месте. 4d61726b646f776e2e436f64652822222c2022766965772229_40726566
хранит входные векторы индексов в объекте SubArray
, который позже может быть использован для косвенной индексации оригинального массива. Поместив макрос @views
перед выражением или блоком кода, любой срез array[...]
в этом выражении будет преобразован для создания представления SubArray
.
BitArray
являются экономичными по пространству "упакованными" булевыми массивами, которые хранят по одному биту на булевое значение. Их можно использовать аналогично массивам Array{Bool}
(которые хранят по одному байту на булевое значение) и их можно конвертировать в/из последних с помощью Array(bitarray)
и BitArray(array)
, соответственно.
Массив называется "с шагом", если он хранится в памяти с четко определенными интервалами (шагами) между его элементами. Массив с шагом с поддерживаемым типом элемента может быть передан во внешнюю (не Julia) библиотеку, такую как BLAS или LAPACK, просто передав его pointer
и шаг для каждого измерения. stride(A, d)
— это расстояние между элементами вдоль измерения d
. Например, встроенный Array
, возвращаемый rand(5,7,2)
, имеет свои элементы, расположенные последовательно в порядке столбцов. Это означает, что шаг первого измерения — расстояние между элементами в одном и том же столбце — равен 1
:
julia> A = rand(5, 7, 2);
julia> stride(A, 1)
1
Шаг второго измерения — это расстояние между элементами в одной строке, пропуская столько элементов, сколько в одном столбце (5
). Аналогично, переход между двумя "страницами" (в третьем измерении) требует пропустить 5*7 == 35
элементов. strides
этого массива — это кортеж из этих трех чисел вместе:
julia> strides(A)
(1, 5, 35)
В этом конкретном случае количество пропущенных элементов в памяти совпадает с количеством линейных индексов, которые были пропущены. Это верно только для смежных массивов, таких как Array
(и других подтипов DenseArray
), и не является общим правилом. Представления с диапазонными индексами являются хорошим примером несмежных массивов с шагом; рассмотрим V = @view A[1:3:4, 2:2:6, 2:-1:1]
. Это представление V
ссылается на ту же память, что и A
, но пропускает и переставляет некоторые из своих элементов. Шаг первого измерения V
равен 3
, потому что мы выбираем только каждую третью строку из нашего оригинального массива:
julia> V = @view A[1:3:4, 2:2:6, 2:-1:1];
julia> stride(V, 1)
3
Этот вид аналогично выбирает каждый второй столбец из нашего оригинального A
— и, следовательно, ему нужно пропустить эквивалент двух пятиэлементных столбцов при перемещении между индексами во втором измерении:
julia> stride(V, 2)
10
Третье измерение интересно, потому что его порядок обратный! Таким образом, чтобы перейти от первой "страницы" ко второй, необходимо двигаться назад в памяти, и поэтому его шаг в этом измерении отрицательный!
julia> stride(V, 3)
-35
Это означает, что pointer
для V
на самом деле указывает на середину блока памяти A
, и он ссылается на элементы как назад, так и вперед в памяти. См. interface guide for strided arrays для получения дополнительной информации о том, как определить свои собственные массивы с шагом. StridedVector
и StridedMatrix
являются удобными псевдонимами для многих встроенных типов массивов, которые считаются массивами с шагом, позволяя им вызывать специализированные реализации, которые используют высоко настроенные и оптимизированные функции BLAS и LAPACK, используя только указатель и шаги.
Стоит подчеркнуть, что шаги касаются смещений в памяти, а не индексации. Если вы хотите преобразовать между линейной (одиночной индексацией) и декартовой (многоиндексной) индексацией, смотрите LinearIndices
и CartesianIndices
.
- 1iid, independently and identically distributed.