Spec-Zone.ru › Julia 1.0

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

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)

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

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,...) возвращает скаляр.

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

Тип результирующего массива зависит от типов вычисленных элементов. Для явного управления типом можно добавить тип перед выражением. Например, мы могли запросить результат с одинарной точностью, написав:

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)

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

Сгенерированные значения могут быть отфильтрованы с помощью ключевого слова if:

julia> [(i,j) for i=1:3 for j=1:i if i+j == 4]
2-element Array{Tuple{Int64,Int64},1}:
 (2, 2)
 (3, 1)

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

Общая синтаксическая конструкция для индексирования n-мерного массива A:

X = A[I_1, I_2, ..., I_n]

где каждый I_k может быть скалярным целым числом, массивом целых чисел или любым другим поддерживаемым индексом. Это включает Colon (:) для выбора всех индексов внутри всего измерения, диапазонов в форме a:c или a:b:c для выбора непрерывных или с шагом подмножеств, и массивов булевых значений для выбора элементов по их true индексам.

Если все индексы являются скалярами, то результат X — это один элемент из массива A. В противном случае X — это массив с тем же числом измерений, что и сумма размерностей всех индексов.

Если все индексы являются векторами, например, то форма X будет (length(I_1), length(I_2), ..., length(I_n)), с местоположением (i_1, i_2, ..., i_n) X содержащим значение A[I_1[i_1], I_2[i_2], ..., I_n[i_n]].

Пример:

julia> A = reshape(collect(1:16), (2, 2, 2, 2))
2×2×2×2 Array{Int64,4}:
[:, :, 1, 1] =
 1  3
 2  4

[:, :, 2, 1] =
 5  7
 6  8

[:, :, 1, 2] =
  9  11
 10  12

[:, :, 2, 2] =
 13  15
 14  16

julia> A[1, 2, 1, 1] # all scalar indices
3

julia> A[[1, 2], [1], [1, 2], [1]] # all vector indices
2×1×2×1 Array{Int64,4}:
[:, :, 1, 1] =
 1
 2

[:, :, 2, 1] =
 5
 6

julia> A[[1, 2], [1], [1, 2], 1] # a mix of index types
2×1×2 Array{Int64,3}:
[:, :, 1] =
 1
 2

[:, :, 2] =
 5
 6

Обратите внимание, как размер результирующего массива отличается в последних двух случаях.

Если I_1 изменено на двумерную матрицу, то X становится n+1-мерным массивом с формой (size(I_1, 1), size(I_1, 2), length(I_2), ..., length(I_n)). Матрица добавляет измерение.

Пример:

julia> A = reshape(collect(1:16), (2, 2, 2, 2));

julia> A[[1 2; 1 2]]
2×2 Array{Int64,2}:
 1  2
 1  2

julia> A[[1 2; 1 2], 1, 2, 1]
2×2 Array{Int64,2}:
 5  6
 5  6

Местоположение (i_1, i_2, i_3, ..., i_{n+1}) содержит значение в A[I_1[i_1, i_2], I_2[i_3], ..., I_n[i_{n+1}]]. Все измерения, индексированные скалярами, удаляются. Например, результат A[2, I, 3] — это массив размером size(I). Его i-й элемент заполнен A[2, I[i], 3].

В качестве особой части этого синтаксиса ключевое слово end может быть использовано для представления последнего индекса каждого измерения в скобках индексирования, как определяется размером самого внутреннего индексируемого массива. Синтаксис индексирования без ключевого слова end эквивалентен вызову getindex:

X = getindex(A, I_1, I_2, ..., I_n)

Пример:

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

julia> x[2:3, 2:end-1]
2×2 Array{Int64,2}:
 6  10
 7  11

julia> x[1, [2 3; 4 1]]
2×2 Array{Int64,2}:
  5  9
 13  1

Пустые диапазоны вида n:n-1 иногда используются для обозначения местоположения между индексами n-1 и n. Например, функция searchsorted использует эту конвенцию для обозначения точки вставки значения, не найденного в отсортированном массиве:

julia> a = [1,2,5,6,7];

julia> searchsorted(a, 4)
3:2

Присваивание

Общая синтаксическая конструкция для присваивания значений в n-мерном массиве A:

A[I_1, I_2, ..., I_n] = X

где каждый I_k может быть скалярным целым числом, массивом целых чисел или любым другим поддерживаемым индексом. Это включает Colon (:) для выбора всех индексов в пределах всего измерения, диапазонов в форме a:c или a:b:c для выбора непрерывных или с шагом подмножеств, и массивов булевых значений для выбора элементов по их true индексам.

Если X — массив, он должен иметь такое же количество элементов, как произведение длин индексов: prod(length(I_1), length(I_2), ..., length(I_n)). Значение в местоположении I_1[i_1], I_2[i_2], ..., I_n[i_n] массива A перезаписывается значением X[i_1, i_2, ..., i_n]. Если X — скаляр, используйте оператор присваивания по элементам .=, чтобы записать значение во все реферируемые местоположения массива A:

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 просто объединяет несколько целых чисел в один объект, представляющий один многомерный индекс. Однако, в сочетании с другими формами индексирования и итераторами, которые возвращают CartesianIndexs, это может привести к очень элегантному и эффективному коду. См. Итерацию ниже, и для некоторых более сложных примеров см. эту запись блога о многомерных алгоритмах и итерации.

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

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

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

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

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

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

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

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

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

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

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

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

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

Итерация

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

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

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

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

julia> A = rand(4,3);

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

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

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

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

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

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

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

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

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

  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 не ограничен массивами (см. документацию функции), он также обрабатывает кортежи и рассматривает любой аргумент, который не является массивом, кортежем или Ref (кроме Ptr) как «скаляр».

julia> convert.(Float32, [1, 2])
2-element Array{Float32,1}:
 1.0
 2.0

julia> ceil.((UInt8,), [1.2 3.4; 5.6 6.7])
2×2 Array{UInt8,2}:
 0x02  0x04
 0x06  0x07

julia> string.(1:3, ". ", ["First", "Second", "Third"])
3-element Array{String,1}:
 "1. First"
 "2. Second"
 "3. Third"

Реализация

Базовый тип массива в Julia — это абстрактный тип AbstractArray{T,N}. Он параметризуется количеством измерений N и типом элементов T. AbstractVector и AbstractMatrix являются псевдонимами для случаев с 1 и 2 измерениями. Операции над AbstractArray объектами определяются с использованием операторов и функций более высокого уровня, способом, независимым от базового хранения. Эти операции, как правило, корректно работают как отказ для любой конкретной реализации массива.

Тип AbstractArray включает в себя все, что похоже на массив, и его реализации могут сильно отличаться от обычных массивов. Например, элементы могут вычисляться по запросу, а не храниться. Однако любой конкретный тип AbstractArray{T,N} должен, как правило, реализовывать по меньшей мере size(A) (возвращающий кортеж Int), getindex(A,i) и getindex(A,i1,...,iN); изменяемые массивы также должны реализовывать setindex!. Рекомендуется, чтобы эти операции имели практически постоянную временную сложность или технически сложность Õ(1), поскольку в противном случае некоторые функции массива могут быть неожиданно медленными. Конкретные типы также обычно должны обеспечивать метод similar(A,T=eltype(A),dims=size(A)), который используется для выделения аналогичного массива для copy и других операций вне места. Независимо от того, как AbstractArray{T,N} представлен внутри, T — это тип объекта, возвращаемого целочисленным индексированием (A[1, ..., 1], когда A не пустой) и N должно быть длиной кортежа, возвращаемого size. Более подробную информацию о определении пользовательских реализаций AbstractArray см. в руководстве по интерфейсу массивов в главе по интерфейсам.

DenseArray является абстрактным подтипом AbstractArray , предназначенным для включения всех массивов, где элементы хранятся непрерывно в порядке столбцов (см. дополнительные примечания в разделе «Рекомендации по производительности»). Тип Array является конкретным примером DenseArray; Vector и Matrix являются псевдонимами для случаев с 1 и 2 измерениями. Очень мало операций реализованы специально для Array за пределами тех, которые необходимы для всех AbstractArray; большая часть библиотеки массивов реализована общим способом, который позволяет всем пользовательским массивам вести себя аналогично.

SubArray — это специализация AbstractArray, которая выполняет индексирование, совмещая память с исходным массивом, а не копируя его. SubArray создается с помощью функции view, которая вызывается так же, как и getindex (с массивом и рядом аргументов индексов). Результат view выглядит так же, как результат getindex, за исключением того, что данные остаются на месте. view хранит входные векторы индексов в объекте SubArray , который позже можно использовать для индексирования исходного массива косвенно. Разместив макрос @views перед выражением или блоком кода, любой срез array[...] в этом выражении будет преобразован для создания представления SubArray вместо него.

BitArray — это экономичные «упакованные» булевы массивы, которые хранят по одному биту на булевое значение. Они могут использоваться аналогично массивам Array{Bool} (которые хранят по одному байту на булево значение), и могут быть преобразованы в/из последних с помощью Array(bitarray) и BitArray(array) соответственно.

«Шагированный» массив хранится в памяти с элементами, расположенными с регулярными смещениями, так что экземпляр с поддерживаемым isbits типом элемента может быть передан внешним функциям C и Fortran, которые ожидают такую структуру памяти. Шагированные массивы должны определять метод strides(A), возвращающий кортеж «шагов» для каждой размерности; предоставленный метод stride(A,k) получает доступ к k-му элементу в этом кортеже. Увеличение индекса размерности k на 1 должно увеличить индекс i getindex(A,i) на stride(A,k). Если предоставлен метод преобразования указателя Base.unsafe_convert(Ptr{T}, A), структура памяти должна соответствовать этим шагам. DenseArray — это очень конкретный пример шагированного массива, где элементы расположены непрерывно, поэтому он предоставляет своим подтипам соответствующее определение strides. Более конкретные примеры можно найти в руководстве по интерфейсу шагированных массивов. StridedVector и StridedMatrix — удобные псевдонимы для многих встроенных типов массивов, которые считаются шагированными массивами, что позволяет им использовать специализированные реализации, которые вызывают высоконастроенные и оптимизированные функции BLAS и LAPACK, используя только указатель и шаги.

Следующий пример вычисляет QR-разложение небольшой части большего массива без создания временных объектов и вызывая соответствующую функцию LAPACK с правильным размером ведущей размерности и параметрами шага.

julia> a = rand(10, 10)
10×10 Array{Float64,2}:
 0.517515  0.0348206  0.749042   0.0979679  …  0.75984     0.950481   0.579513
 0.901092  0.873479   0.134533   0.0697848     0.0586695   0.193254   0.726898
 0.976808  0.0901881  0.208332   0.920358      0.288535    0.705941   0.337137
 0.657127  0.0317896  0.772837   0.534457      0.0966037   0.700694   0.675999
 0.471777  0.144969   0.0718405  0.0827916     0.527233    0.173132   0.694304
 0.160872  0.455168   0.489254   0.827851   …  0.62226     0.0995456  0.946522
 0.291857  0.769492   0.68043    0.629461      0.727558    0.910796   0.834837
 0.775774  0.700731   0.700177   0.0126213     0.00822304  0.327502   0.955181
 0.9715    0.64354    0.848441   0.241474      0.591611    0.792573   0.194357
 0.646596  0.575456   0.0995212  0.038517      0.709233    0.477657   0.0507231

julia> b = view(a, 2:2:8,2:2:4)
4×2 view(::Array{Float64,2}, 2:2:8, 2:2:4) with eltype Float64:
 0.873479   0.0697848
 0.0317896  0.534457
 0.455168   0.827851
 0.700731   0.0126213

julia> (q, r) = qr(b);

julia> q
4×4 LinearAlgebra.QRCompactWYQ{Float64,Array{Float64,2}}:
 -0.722358    0.227524  -0.247784    -0.604181
 -0.0262896  -0.575919  -0.804227     0.144377
 -0.376419   -0.75072    0.540177    -0.0541979
 -0.579497    0.230151  -0.00552346   0.781782

julia> r
2×2 Array{Float64,2}:
 -1.20921  -0.383393
  0.0      -0.910506

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

Spec-Zone.ru

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