Spec-Zone.ru › Julia 1.8

Многомерные массивы

Julia, как и большинство языков технического вычисления, предоставляет реализацию массивов первого класса. Большинство языков технического вычисления уделяют много внимания реализации массивов в ущерб другим контейнерам. Julia не рассматривает массивы каким-то особым образом. Библиотека массивов реализована почти полностью на самом языке Julia и черпает свою производительность из компилятора, как и любой другой код, написанный на Julia. Таким образом, также можно определить пользовательские типы массивов, унаследовав от AbstractArray. Более подробную информацию об имплементации пользовательского типа массива см. в разделе руководства по интерфейсу AbstractArray.

Массив — это коллекция объектов, хранящихся в многомерной сетке. Разрешены нулевые массивы, см. данную запись FAQ. В общем случае массив может содержать объекты типа Any. Для большинства вычислительных целей массивы должны содержать объекты более конкретного типа, такие как Float64 или Int32.

В общем, в отличие от многих других языков технического вычисления, Julia не ожидает, что программы будут написаны в векторизованном стиле для повышения производительности. Компилятор Julia использует вывод типов и генерирует оптимизированный код для скалярной индексации массивов, что позволяет писать программы в удобном и читабельном стиле без ущерба для производительности и иногда с меньшим использованием памяти.

Во всех функциях Julia аргументы передаются по ссылке (т.е. через указатели). Некоторые языки технического вычисления передают массивы по значению, и хотя это предотвращает случайное изменение вызывающей стороной значения, переданного вызываемым объектом, это затрудняет избежание нежелательного копирования массивов. По соглашению, имя функции, оканчивающееся на !, указывает, что она будет изменять или уничтожать значение одного или нескольких своих аргументов (см., например, sort и sort!). Вызываемые объекты должны выполнять явное копирование, чтобы гарантировать, что они не изменят входные данные, которые они не намерены изменять. Многие не изменяющие функции реализуются путем вызова функции с тем же именем с добавленным ! в конце на явной копии входных данных и возврата этой копии.

Основные функции

Функция Описание
eltype(A) тип элементов, содержащихся в A
length(A) количество элементов в A
ndims(A) количество измерений A
size(A) кортеж, содержащий размеры A
size(A,n) размер A по измерению n
axes(A) кортеж, содержащий допустимые индексы A
axes(A,n) диапазон, выражающий допустимые индексы по измерению n
eachindex(A) эффективный итератор для посещения каждой позиции в A
stride(A,k) шаг (линейное расстояние между смежными элементами) по измерению k
strides(A) кортеж шагов по каждому измерению

Создание и инициализация

Предоставлено множество функций для создания и инициализации массивов. В приведенном ниже списке таких функций вызовы с аргументом dims... могут принимать либо один кортеж размеров измерений, либо серию размеров измерений, переданных в качестве переменного числа аргументов. Большинство из этих функций также принимают первый вход T, который является типом элементов массива. Если тип T опущен, он будет по умолчанию равен Float64.

Функция Описание
Array{T}(undef, dims...) неинициализированный плотный Array
zeros(T, dims...) массив всех нулей
ones(T, dims...) массив всех единиц
trues(dims...) массив BitArray со всеми значениями true
falses(dims...) массив со всеми значениями BitArray false
reshape(A, dims...) массив, содержащий те же данные, что и A, но с другими размерами
copy(A) копия A
deepcopy(A) копия A, рекурсивно копирующая ее элементы
similar(A, T, dims...) неинициализированный массив того же типа, что и A (плотный, разреженный и т. д.), но с указанным типом элементов и размерами. Вторые и третьи аргументы являются необязательными, по умолчанию принимая тип элементов и размеры A в случае их опущения.
reinterpret(T, A) массив с теми же двоичными данными, что и A, но с типом элементов T
rand(T, dims...) массив со случайными, независимыми и одинаково распределёнными значениями в полуоткрытом интервале $[0, 1)$
randn(T, dims...) массив со случайными, независимыми и стандартно нормально распределёнными значениями
Matrix{T}(I, m, n) матрица тождества m×n. Требует using LinearAlgebra для I.
range(start, stop, n) диапазон n линейно расположенных элементов от start до stop
fill!(A, x) заполнение массива A значением x
fill(x, dims...) массив, заполненный значением x. В частности, fill(x) создаёт нульмерный Array, содержащий 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.

Литералы массивов

Массивы также можно создавать непосредственно с помощью квадратных скобок; синтаксис [A, B, C, ...] создаёт одномерный массив (т. е. вектор), содержащий перечисленные через запятую аргументы в качестве своих элементов. Тип элемента (eltype) результирующего массива автоматически определяется типами аргументов внутри скобок. Если все аргументы имеют один и тот же тип, то это и будет его типом. Если у всех аргументов есть общий тип продвижения, то они преобразуются к этому типу с использованием convert, и этот тип будет типом массива. В противном случае создается неоднородный массив, который может содержать всё — Vector{Any}; это включает литерал [], где нет аргументов.

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> []
Any[]

Конкатенация

Если аргументы внутри квадратных скобок разделены одиночными точками с запятой (;) или новыми строками вместо запятых, то их содержимое вертикально объединяется вместе вместо того, чтобы аргументы использовались как элементы сами по себе.

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. Эти синтаксисы являются сокращениями для вызовов функций, которые сами являются удобными функциями:

Синтаксис Функция Описание
cat объединение входных массивов вдоль размерностей k
[A; B; C; ...] vcat сокращение для `cat(A...; dims=1)
[A B C ...] hcat сокращение для `cat(A...; dims=2)
[A B; C D; ...] hvcat одновременное вертикальное и горизонтальное объединение
[A; C;; B; D;;; ...] hvncat одновременное объединение n-мерных данных, где количество точек с запятой указывает размерность для объединения

Массивы с типом данных

Массив с определенным типом элементов может быть создан с помощью синтаксиса 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

Понимания

Понимания предоставляют общий и мощный способ создания массивов. Синтаксис понимания похож на обозначение построения множеств в математике:

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 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 из окружающего пространства имен. Захваченные переменные могут создавать проблемы производительности; см. советы по производительности.

Диапазоны в генераторах и пониманиях могут зависеть от предыдущих диапазонов путем написания нескольких ключевых слов 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)

В таких случаях результат всегда является одномерным.

Сгенерированные значения могут быть отфильтрованы с помощью ключевого слова 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)

Индексирование

Общий синтаксис индексирования 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 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

Индексированное присваивание

Общий синтаксис для присваивания значений в 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 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

Поддерживаемые типы индексов

В выражении A[I_1, I_2, ..., I_n], каждый I_k может быть скалярным индексом, массивом скалярных индексов или объектом, представляющим массив скалярных индексов и который может быть преобразован к таковому с помощью to_indices:

  1. Скалярный индекс. По умолчанию это включает:
    • Небулевы целые числа
    • CartesianIndex{N}, которые ведут себя как N-кортеж целых чисел, охватывающих несколько измерений (см. подробности ниже)
  2. Массив скалярных индексов. Это включает:
    • Векторы и многомерные массивы целых чисел
    • Пустые массивы, такие как [], которые не выбирают никаких элементов
    • Диапазоны, такие как a:c или a:b:c, которые выбирают непрерывные или с шагом подмножества от a до c (включительно)
    • Любой пользовательский массив скалярных индексов, который является подтипом AbstractArray
    • Массивы CartesianIndex{N} (см. подробности ниже)
  3. Объект, представляющий массив скалярных индексов и который может быть преобразован к таковому с помощью 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

Декартовы индексы

Специальный объект 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 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

Это можно выразить гораздо проще с помощью точечного широковещательного умножения и объединения его с обычным целочисленным индексом (вместо выделения первой 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 или массивы таких.

Логическое индексирование

Часто называемое логическим индексированием или индексированием с логической маской, индексирование булевым массивом выбирает элементы в позициях, где его значения 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 Matrix{Int64}:
 2  6  10  14
 3  7  11  15

julia> mask = map(ispow2, x)
4×4 Matrix{Bool}:
 1  0  0  0
 1  0  0  0
 0  0  0  0
 1  1  0  1

julia> x[mask]
5-element Vector{Int64}:
  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 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 тоже.

Пропущенные и дополнительные индексы

В дополнение к линейному индексированию, 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

Итерация

Рекомендуемые способы итерации по всему массиву

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 использовать целые числа. Если вы не укажете это свойство, будет использовано значение по умолчанию IndexCartesian().

Массивы и векторизованные операторы и функции

Для массивов поддерживаются следующие операторы:

  1. Унарные арифметические – -, +
  2. Бинарные арифметические – -, +, *, /, \, ^
  3. Сравнения – ==, !=, ≈ (isapprox), ≉

Для удобной векторизации математических и других операций Julia предоставляет синтаксис с точкой f.(args...), например, sin.(x) или min.(x,y), для поэлементных операций над массивами или комбинациями массивов и скаляров (операция векторного расширения); эти операции имеют дополнительное преимущество «слияния» в один цикл при комбинировании с другими вызовами с точкой, например, sin.(cos.(x)).

Кроме того, каждый бинарный оператор поддерживает версию с точкой, которую можно применять к массивам (и комбинациям массивов и скаляров) в таких операциях векторного расширения, например, z .== sin.(x .* y).

Обратите внимание, что сравнения, такие как ==, работают с целыми массивами, давая один булевый ответ. Используйте операторы с точкой, такие как .==, для поэлементных сравнений. (Для операций сравнения, таких как <, только версия поэлементного сравнения .< применима к массивам.)

Также обратите внимание на разницу между max.(a,b), которая broadcast поэлементно max над a и b, и maximum(a), которая находит наибольшее значение внутри a. Такая же связь существует для min.(a,b) и minimum(a).

Векторное расширение

Иногда бывает полезно выполнять поэлементные бинарные операции над массивами разного размера, например, добавление вектора к каждому столбцу матрицы. Неэффективный способ сделать это — дублировать вектор до размера матрицы:

julia> a = rand(2,1); A = rand(2,3);

julia> repeat(a,1,3)+A
2×3 Array{Float64,2}:
 1.20813  1.82068  1.25387
 1.56851  1.86401  1.67846

Это расточительно при больших размерах, поэтому Julia предоставляет broadcast, который расширяет одноэлементные размерности аргументов массива для соответствия соответствующей размерности в другом массиве без использования дополнительной памяти и применяет заданную функцию поэлементно:

julia> broadcast(+, a, A)
2×3 Array{Float64,2}:
 1.20813  1.82068  1.25387
 1.56851  1.86401  1.67846

julia> b = rand(1,2)
1×2 Array{Float64,2}:
 0.867535  0.00457906

julia> broadcast(+, a, b)
2×2 Array{Float64,2}:
 1.71056  0.847604
 1.73659  0.873631

Операторы с точкой, такие как .+ и .* эквивалентны вызовам broadcast (за исключением того, что они объединяются, как описано выше). Также существует функция broadcast! для указания явного назначения (к которому также можно получить доступ по принципу объединения с помощью .= присваивания). Фактически, f.(args...) эквивалентно broadcast(f, args...), что обеспечивает удобный синтаксис для векторизации любой функции (синтаксис с точкой). Вложенные «вызовы с точкой» f.(...) (включая вызовы .+ и т. д.) автоматически объединяются в один вызов broadcast.

Кроме того, broadcast не ограничивается массивами (см. документацию функции); она также обрабатывает скаляры, кортежи и другие коллекции. По умолчанию только некоторые типы аргументов рассматриваются как скаляры, включая (но не ограничиваясь) Numberы, Stringы, Symbolы, Typeы, Functionы и некоторые общие одиночные значения, такие как missing и nothing. Все остальные аргументы итерационно обрабатываются или индексируются поэлементно.

julia> convert.(Float32, [1, 2])
2-element 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])

Реализация

Базовый тип массива в 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 см. руководство по интерфейсу массивов в разделе интерфейсов.

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) соответственно.

Массив «строковый», если он хранится в памяти с определенными расстояниями (шагами) между элементами. Строковый массив с поддерживаемым типом элементов может быть передан внешней (не-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, и он ссылается на элементы, как вперёд, так и назад в памяти. Более подробную информацию о определении собственных строченных массивов см. в руководстве по интерфейсу строченных массивов. StridedVector и StridedMatrix являются удобными псевдонимами для многих встроенных типов массивов, которые считаются строченными массивами, позволяя им перенаправлять вызовы на специализированные реализации, вызывающие высоконастроенные и оптимизированные функции BLAS и LAPACK, используя только указатель и шаги.

Стоит подчеркнуть, что шаги связаны со смещениями в памяти, а не с индексами. Если вам нужно преобразовать линейную (одноиндексную) индексацию в декартову (многоиндексную), см. LinearIndices и CartesianIndices.

  • 1iid, независимые и одинаково распределённые.

© 2009–2022 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.8/manual/arrays/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API