Spec-Zone.ru › Julia 1.3

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

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

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)

Скалярные значения, переданные в эти функции, рассматриваются как массивы из 1 элемента. Например,

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:

  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 Array{Int64,2}:
 1   7  13
 3   9  15
 5  11  17

julia> A[4]
7

julia> A[[2, 5, 8]]
3-element Array{Int64,1}:
  3
  9
 15

julia> A[[1 4; 3 8]]
2×2 Array{Int64,2}:
 1   7
 5  15

julia> A[[]]
0-element Array{Int64,1}

julia> A[1:2:5]
3-element Array{Int64,1}:
 1
 5
 9

julia> A[2, :]
3-element Array{Int64,1}:
  3
  9
 15

julia> A[:, 3]
3-element Array{Int64,1}:
 13
 15
 17

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

Специальный объект CartesianIndex{N} представляет скалярный индекс, который ведёт себя как N-кортеж целых чисел, охватывающий несколько размерностей. Например:

julia> A = reshape(1:32, 4, 4, 2);

julia> A[3, 2, 1]
7

julia> A[CartesianIndex(3, 2, 1)] == A[3, 2, 1] == 7
true

Рассматривая это изолированно, это может показаться относительно тривиальным; CartesianIndex просто собирает несколько целых чисел вместе в один объект, который представляет собой один многомерный индекс. Однако, в сочетании с другими формами индексирования и итераторами, которые возвращают CartesianIndexы, это может привести к очень элегантному и эффективному коду. См. Итерацию ниже, а для некоторых более сложных примеров см. эту запись блога о многомерных алгоритмах и итерации.

Также поддерживаются массивы CartesianIndex{N}. Они представляют собой набор скалярных индексов, каждый из которых охватывает N измерений, что позволяет выполнять индексирование, иногда называемое точечным индексированием. Например, это позволяет получить доступ к диагональным элементам из первой «страницы» A из вышеприведенного примера:

julia> page = A[:,:,1]
4×4 Array{Int64,2}:
 1  5   9  13
 2  6  10  14
 3  7  11  15
 4  8  12  16

julia> page[[CartesianIndex(1,1),
             CartesianIndex(2,2),
             CartesianIndex(3,3),
             CartesianIndex(4,4)]]
4-element Array{Int64,1}:
  1
  6
 11
 16

Это можно выразить намного проще с помощью векторного умножения и объединения его с обычным целочисленным индексом (вместо выделения первой page из A в качестве отдельного шага). Его даже можно объединить с : для одновременного извлечения обеих диагоналей с двух страниц:

julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), 1]
4-element Array{Int64,1}:
  1
  6
 11
 16

julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), :]
4×2 Array{Int64,2}:
  1  17
  6  22
 11  27
 16  32
Предупреждение

CartesianIndex и массивы CartesianIndex несовместимы с ключевым словом end для представления последнего индекса измерения. Не используйте end в выражениях индексирования, которые могут содержать CartesianIndex или массивы из них.

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

Часто называемое логическим индексированием или индексированием с логической маской, индексирование булевым массивом выбирает элементы в тех индексах, где его значения true. Индексирование булевым вектором B по сути то же самое, что индексирование вектором целых чисел, возвращаемым findall(B). Аналогично, индексирование N-мерным булевым массивом по сути то же самое, что индексирование вектором CartesianIndex{N}ов, где его значения true. Логический индекс должен быть вектором той же длины, что и измерение, в которое он индексирует, или он должен быть единственным предоставленным индексом и соответствовать размеру и размерности массива, в который он индексирует. Обычно более эффективно использовать булевы массивы в качестве индексов напрямую, вместо того, чтобы сначала вызывать findall.

julia> x = reshape(1:16, 4, 4)
4×4 reshape(::UnitRange{Int64}, 4, 4) with eltype Int64:
 1  5   9  13
 2  6  10  14
 3  7  11  15
 4  8  12  16

julia> x[[false, true, true, false], :]
2×4 Array{Int64,2}:
 2  6  10  14
 3  7  11  15

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

julia> x[mask]
5-element Array{Int64,1}:
  1
  2
  4
  8
 16

Количество индексов

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

Обычный способ индексирования в N-мерный массив — использование ровно N индексов; каждый индекс выбирает положение(я) в своем конкретном измерении. Например, в трехмерном массиве A = rand(4, 3, 2), A[2, 3, 1] выберет число во второй строке третьего столбца в первой «странице» массива. Это часто называется декартовым индексированием.

Линейное индексирование

Когда предоставлен ровно один индекс i, этот индекс больше не представляет положение в конкретном измерении массива. Вместо этого он выбирает i-й элемент, используя порядок обхода по столбцам, который линейно охватывает весь массив. Это известно как линейное индексирование. По сути, это рассматривает массив так, как будто он был преобразован в одномерный вектор с помощью vec.

julia> A = [2 6; 4 7; 3 1]
3×2 Array{Int64,2}:
 2  6
 4  7
 3  1

julia> A[5]
7

julia> vec(A)[5]
7

Линейный индекс в массив A может быть преобразован в CartesianIndex для декартова индексирования с CartesianIndices(A)[i] (см. CartesianIndices), а набор N декартовых индексов может быть преобразован в линейный индекс с LinearIndices(A)[i_1, i_2, ..., i_N] (см. LinearIndices).

julia> CartesianIndices(A)[5]
CartesianIndex(2, 2)

julia> LinearIndices(A)[2, 2]
5

Важно отметить, что существует очень большая асимметрия в производительности этих преобразований. Преобразование линейного индекса в набор декартовых индексов требует деления и взятия остатка, тогда как преобразование в обратную сторону — это просто умножения и сложения. В современных процессорах целочисленное деление может быть в 10-50 раз медленнее, чем умножение. Хотя некоторые массивы — например, Array сам — реализуются с помощью линейного блока памяти и напрямую используют линейный индекс в своих реализациях, другие массивы — например, Diagonal — нуждаются в полном наборе декартовых индексов для выполнения поиска (см. IndexStyle, чтобы узнать, какой из них какой). Таким образом, при итерации по всему массиву гораздо лучше использовать итерацию по eachindex(A), а не 1:length(A). Это не только будет намного быстрее в случаях, когда A IndexCartesian, но также будет поддерживать OffsetArrays.

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

Помимо линейного индексирования, N-мерный массив может быть проиндексирован с меньшим или большим количеством чем N индексов в определенных ситуациях.

Индексы могут быть опущены, если все хвостовые измерения, которые не индексируются, имеют длину один. Другими словами, хвостовые индексы могут быть опущены только в том случае, если существует только одно возможное значение, которое эти опущенные индексы могли бы принимать для выражения индексирования внутри границ. Например, четырехмерный массив с размером (3, 4, 2, 1) может быть индексирован только тремя индексами, так как измерение, которое пропускается (четвертое измерение), имеет длину один. Обратите внимание, что линейное индексирование имеет приоритет перед этим правилом.

julia> A = reshape(1:24, 3, 4, 2, 1)
3×4×2×1 reshape(::UnitRange{Int64}, 3, 4, 2, 1) with eltype Int64:
[:, :, 1, 1] =
 1  4  7  10
 2  5  8  11
 3  6  9  12

[:, :, 2, 1] =
 13  16  19  22
 14  17  20  23
 15  18  21  24

julia> A[1, 3, 2] # Omits the fourth dimension (length 1)
19

julia> A[1, 3] # Attempts to omit dimensions 3 & 4 (lengths 2 and 1)
ERROR: BoundsError: attempt to access 3×4×2×1 reshape(::UnitRange{Int64}, 3, 4, 2, 1) with eltype Int64 at index [1, 3]

julia> A[19] # Linear indexing
19

При опущении всех индексов с A[], эта семантика предоставляет простой способ извлечения единственного элемента в массиве и одновременной гарантии, что в массиве был только один элемент.

Аналогично, может быть предоставлено больше чем N индексов, если все индексы за пределами размерности массива 1 (или, более общо, являются первым и единственным элементом axes(A, d) где d — это номер конкретного измерения). Это позволяет индексировать векторы как матрицы с одним столбцом, например:

julia> A = [8,6,7]
3-element Array{Int64,1}:
 8
 6
 7

julia> A[2,1]
6

Итерация

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

for a in A
    # Do something with the element a
end

for i in eachindex(A)
    # Do something with i and/or A[i]
end

Первый конструкт используется, когда вам нужно значение, но не индекс, каждого элемента. Во втором конструкте, i будет Int если A — это тип массива с быстрым линейным индексированием; в противном случае он будет CartesianIndex.

julia> A = rand(4,3);

julia> B = view(A, 1:3, 2:3);

julia> for i in eachindex(B)
           @show i
       end
i = CartesianIndex(1, 1)
i = CartesianIndex(2, 1)
i = CartesianIndex(3, 1)
i = CartesianIndex(1, 2)
i = CartesianIndex(2, 2)
i = CartesianIndex(3, 2)

В отличие от for i = 1:length(A), итерация с помощью eachindex предоставляет эффективный способ итерации по любому типу массива.

Свойства массивов

Если вы создаёте пользовательский тип AbstractArray, вы можете указать, что он имеет быстрое линейное индексирование, используя

Base.IndexStyle(::Type{<:MyArray}) = IndexLinear()

Эта настройка заставит eachindex итерацию по MyArray использовать целые числа. Если вы не укажете это свойство, будет использоваться значение по умолчанию IndexCartesian().

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

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

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

«Узлованный» массив хранится в памяти с элементами, расположенными с регулярными смещениями, так что экземпляр с поддерживаемым типом элемента 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–2020 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.3.1/manual/arrays/

Spec-Zone.ru

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