Документация Engee

Массивы, вектора и матрицы в Engee

Страница в процессе разработки.

Введение

Для работы с данными, параметрами и сложными структурами в Engee используются массивы, вектора и матрицы. Эти структуры, реализованные на языке Julia, позволяют эффективно хранить и обрабатывать числовую информацию, включая результаты вычислений, сигналы и параметры моделей. В этой статье рассматриваются особенности каждого из них.

В Julia нумерация элементов массивов, векторов и матриц начинается с 1, а не с 0, как в некоторых других языках. Последний элемент имеет индекс end. Это соответствует математическим традициям, где индексация также начинается с единицы. Например, первый элемент массива a = [1, 2, 3] доступен через a[1], а последний — через a[end]. Более подробно смотрите в разделе ниже.

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

A = rand(2, 2, 3)
Вывод
2×2×3 Array{Float64, 3}:
[:, :, 1] =
 0.159525  0.787948
 0.358068  0.124023

[:, :, 2] =
 0.228296  0.292929
 0.712896  0.0253828

[:, :, 3] =
 0.3547    0.837655
 0.474441  0.201402

Массивы бывают динамическими и статическими:

  • Динамические массивы — это массивы, размер которых может изменяться во время выполнения программы. Они хранятся в памяти с учетом возможного расширения, что делает их более гибкими, но менее оптимальными для задач с интенсивными вычислениями. Пример:

    dynamic_array = [1, 2, 3]
    dynamic_array[2] = 5 # Присваивание значения
    push!(dynamic_array, 4) # Добавление элемента
    deleteat!(dynamic_array, 3) # Удаление элемента с индексом 3
    Вывод
    4-element Vector{Int64}:
     1
     5
     3
     4
  • Статические массивы — это массивы фиксированного размера, который задается на этапе создания. Они хранятся в памяти как непрерывный блок данных, что позволяет выполнять операции быстрее за счет минимизации накладных расходов на управление памятью.

    Статические массивы эффективны, когда их размер меньшее 100-200 элементов. Если размер больше 100-200 элементов, то обычно используют динамические массивы.

    В языке Julia статические массивы предоставляются пакетом StaticArrays.jl. Поэтому, чтобы их использовать, необходимо подключить этот пакет с помощью команды using StaticArrays. Пример:

    using StaticArrays
    static_array = @SVector [1, 2, 3]
    Вывод
    3-element SVector{3, Int64} with indices SOneTo(3):
     1
     2
     3

    В коде использовался макрос[1] @SVector, который создает статический одномерный массив (вектор) фиксированного размера. Существуют и другие полезные макросы для статических массивов:

    Макросы статических массивов
    • @SVector — создает неизменяемый статический вектор фиксированного размера. Пример: @SVector [1, 2, 3].

    • @MVector — создает изменяемый (mutable) статический вектор фиксированного размера. Пример: @MVector [1, 2, 3].

    • @SMatrix — создает неизменяемую статическую матрицу фиксированного размера. Пример: @SMatrix [1 2; 3 4].

    • @MMatrix - создает изменяемую (mutable) статическую матрицу фиксированного размера. Пример: @MMatrix [1 2; 3 4].

    • @SizedArray — преобразует обычный массив в статический массив с фиксированным размером. Пример: @SizedArray rand(2, 2).

    • @SArray — создает общий неизменяемый статический массив, который может быть вектором, матрицей или массивом большей размерности. Пример: @SArray rand(2, 2, 2).

    • @MArray — создает общий изменяемый статический массив. Пример: @MArray rand(2, 2, 2).

    • @StaticArray — универсальный макрос, который создает неизменяемые массивы любых форм. Пример: @StaticArray rand(2, 2).

    • @MutableStaticArray — универсальный макрос для создания изменяемых статических массивов. Пример: @MutableStaticArray rand(2, 2).

    • @zeros — создает статический массив, заполненный нулями. Пример: @zeros(SVector, 3) создает вектор из трех нулей.

    • @ones — создает статический массив, заполненный единицами. Пример: @ones(SMatrix, 2, 2) создает матрицу 2×2 из единиц.

    • @fill — создает статический массив, заполненный заданным значением. Пример: @fill(SVector, 3, 42) создает вектор из трех элементов со значением 42.

    • @static — оптимизирует выполнение блоков кода на этапе компиляции, применяя свойства статических массивов. Пример:

      @static if length(SVector(1, 2, 3)) == 3
      println("Размер вектора равен 3")
      end

В отличие от C++, массивы в Julia могут хранить элементы разных типов. Если типы элементов можно свести к общему (например Rational и Complex), то Julia автоматически выполнит приведение типов. Например:

[1//2, 3 + 4im]  # Автоматически преобразуется в 2-element Vector{Complex{Rational{Int64}}}:

Если типы элементов нельзя свести к общему (например, число и строка), то Julia приведет массив к типу Any, который является объединением всех типов:

[1, "строка"]  # Тип массива — Vector{Any}

Это делает массивы в Julia гибкими, но стоит помнить, что использование Any может снизить производительность, так как компилятор не сможет оптимизировать код для конкретного типа. Подробнее см. здесь.

Вектор — это одномерный массив, представляющий собой последовательность элементов, доступ к которым осуществляется с помощью индексов. В Julia вектор можно создать в виде вектора-столбца или вектора-строки, но их синтаксис и тип данных будут различаться:

  • Вектор-столбец создается с использованием запятых или записи в столбик:

    v = [1, 2, 3]  # Вектор-столбец

    или

    v = [1
         2
         3]  # Вектор-столбец
    Вывод
    3-element Vector{Int64}:
     1
     2
     3
  • В Julia нет отдельного типа для вектора-строки. Вместо этого строка чисел представляется как матрица размерности 1 × N:

    r = [1 2 3]  # Матрица 1 × 3
    Вывод
    1×3 Matrix{Int64}:
     1  2  3

В Julia разделители в массивах определяют их структуру:

  • Запятая (,) используется для создания векторов (одномерных массивов). Элементы разделяются запятыми:

    v = [1, 2, 3]  # Вектор-столбец
  • Точка с запятой (;) используется для создания матриц (двумерных массивов). Она разделяет строки, аналогично функции vcat:

    m = [1 2 3; 4 5 6]  # Матрица 2 × 3

Матрица — это двумерный массив, представляющий таблицу чисел или других объектов, организованных в строки и столбцы. В Julia матрицы создаются как массивы с двумя измерениями:

M = [1 2 3; 4 5 6]
Вывод
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

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

Отличия между массивами, векторами и матрицами

Размерность:

  • Массив — контейнер с произвольным количеством измерений. Массивы имеют произвольное количество измерений, и size(A) может возвращать, например, (2, 2, 3).

  • Вектор — одномерный массив. У векторов всегда одна размерность, а функция size(v) возвращает кортеж (n,), где n — длина вектора.

  • Матрица — двумерный массив. У матриц размерность равна двум, и size(M) возвращает кортеж (n, m), где n — число строк, а m — число столбцов.

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

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

  1. Особенности реализации:

    • Методы и функции для работы с массивами применимы и к векторам, и к матрицам. Однако для каждого типа данных существуют свои оптимизированные алгоритмы.

    • В Julia вектора и матрицы являются частными случаями массивов, где вектор имеет размерность 1, а матрица — 2.

  2. Инициализация и синтаксис:

    • Массив с произвольной размерностью создается функцией Array или с помощью генераторов (выражения в виде […​ for …​]). Например, для Array:

      A = Array{Float64}(undef, 2, 2, 3)

      Здесь undef — это ключевое слово, используется для создания массива без инициализации элементов. Это означает, что память для массива будет выделена, но значения его элементов останутся неопределенными (могут быть случайными).

      Пример с генераторами:

      array = [i^2 for i in 1:5] # Каждый элемент массива - квадрат числа от 1 до 5
    • Вектор создается как одномерный массив, элементы перечисляются через запятую:

      v = [1, 2, 3]
    • Матрица записывается в виде строк, разделенных точкой с запятой (;), а элементы в строках разделяются пробелами или табуляцией:

      M = [1 2 3; 4 5 6]
  3. Хранение в памяти:

    • Массивы с большим количеством измерений используют более общий подход к хранению данных, что может увеличивать накладные расходы при выполнении операций.


Описанные выше различия формируют конкретные области применения:

  • Массив используется для моделирования более сложных данных: трехмерных моделей, массивов изображений или многомерных временных рядов.

  • Вектор чаще всего применяется для одномерных данных: временных рядов, координат, сигналов.

  • Матрица представляет двумерные структуры: изображения, таблицы, системы линейных уравнений.

Основные функции для работы с массивами, векторами и матрицами

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

Функции, помеченные символом * не является частью стандартной библиотеки. Для работы с ними установите соответствующий пакет Julia:

import Pkg
Pkg.add("имя пакета")

Для предоставления доступа к элементам таких функций используются операторы import и using и имя пакета. Подробнее читайте здесь.

Ниже представлен перечень основных функций с кратким описанием и примерами:

Создание
Создание

Имя функции

Описание

Пример

zeros

Создает массив, элементы которого равны нулю.

Тип создаваемого объекта зависит от переданных аргументов: одномерный массив (вектор), двумерный массив (матрица) или многомерный массив.

zeros(3)
zeros(2, 2)
zeros(2, 2, 3)

здесь:

  • zeros(3) — создает вектор [0.0, 0.0, 0.0]

  • zeros(2, 2) — создает матрицу 2x2

  • zeros(2, 2, 3) — создает трехмерный массив

ones

Создает массив, элементы которого равны единице.

Тип создаваемого объекта зависит от переданных аргументов: одномерный массив (вектор), двумерный массив (матрица) или многомерный массив.

ones(3)
ones(2, 2)
ones(2, 2, 3)

здесь:

  • ones(3) — создает вектор [1.0, 1.0, 1.0] (трех элементов, все равны 1.0).

  • ones(2, 2) — создает матрицу 2x2:

    2×2 Matrix{Float64}:
     1.0  1.0
     1.0  1.0
  • ones(2, 2, 3) — создает трехмерный массив размером 2x2x3, где все элементы равны 1.0:

    2×2×3 Array{Float64, 3}:
    [:, :, 1] =
     1.0  1.0
     1.0  1.0
    
    [:, :, 2] =
     1.0  1.0
     1.0  1.0
    
    [:, :, 3] =
     1.0  1.0
     1.0  1.0

rand

Создает массив, заполненный случайными числами с равномерным распределением в интервале [0, 1). Тип создаваемого объекта зависит от переданных аргументов: одномерный массив (вектор), двумерный массив (матрица) или многомерный массив.

rand(3)
rand(2, 2)
rand(2, 2, 3)

здесь:

  • rand(3) — создает вектор из трех случайных чисел, например [0.574, 0.225, 0.639].

    3-element Vector{Float64}:
     0.5741275064461032
     0.2253528689201394
     0.6398495562276317
  • rand(2, 2) — создает матрицу 2x2, заполненную случайными числами, например:

    2×2 Matrix{Float64}:
     0.526635  0.885571
     0.674019  0.869255
  • rand(2, 2, 3) — создает трехмерный массив размером 2x2x3, где элементы — случайные числа, например:

    2×2×3 Array{Float64, 3}:
    [:, :, 1] =
     0.300142  0.594199
     0.125753  0.834848
    
    [:, :, 2] =
     0.707155  0.859128
     0.296165  0.0470918
    
    [:, :, 3] =
     0.220695  0.124332
     0.217407  0.616591

fill

Создает массив, заполненный указанным значением. Тип создаваемого объекта зависит от переданных аргументов: одномерный массив (вектор), двумерный массив (матрица) или многомерный массив.

fill(7, 3)
fill(5, 2, 2)
fill(75, 2, 2, 3)

здесь:

  • fill(7, 3) — создает вектор из трех элементов, где каждый элемент равен 7, например [7, 7, 7].

  • fill(5, 2, 2) — создает матрицу 2x2, заполненную значением 5:

    2×2 Matrix{Int64}:
     5  5
     5  5
  • fill(75, 2, 2, 3) — создает трехмерный массив размером 2x2x3, где каждый элемент равен 75:

    2×2×3 Array{Int64, 3}:
    [:, :, 1] =
     75  75
     75  75
    
    [:, :, 2] =
     75  75
     75  75
    
    [:, :, 3] =
     75  75
     75  75

collect

Создает массив путем преобразования заданного диапазона или итерируемого объекта. Тип создаваемого объекта зависит от используемого ввода.

collect(1:3)
collect(reshape(1:4, 2, 2))
collect(reshape(1:12, 2, 2, 3))

здесь:

  • collect(1:3) — преобразует диапазон 1:3 в вектор [1, 2, 3].

  • collect(reshape(1:4, 2, 2)) — преобразует диапазон 1:4 в матрицу 2x2:

    2×2 Matrix{Int64}:
     1  3
     2  4
  • collect(reshape(1:12, 2, 2, 3)) — преобразует диапазон 1:12 в трехмерный массив размером 2x2x3:

    2×2×3 Array{Int64, 3}:
    [:, :, 1] =
     1  3
     2  4
    
    [:, :, 2] =
     5  7
     6  8
    
    [:, :, 3] =
      9  11
     10  12
Изменение структуры
Изменение структуры

Имя функции

Описание

Пример

push!

Добавляет элемент в конец одномерного массива (вектора). Применимо только к изменяемым (динамическим) массивам.

Также для добавления элемента может использоваться индекс end.

v = [1, 2, 3]
push!(v, 4)

m = reshape(1:6, 2, 3)
# push! нельзя применить к матрице или многомерному массиву.

здесь:

  • v = [1, 2, 3] — создает вектор [1, 2, 3].

  • push!(v, 4) — добавляет элемент 4 в конец вектора. После операции: [1, 2, 3, 4].

  • push! нельзя применить к матрице m или многомерному массиву, так как они имеют фиксированные размеры и требуют изменения структуры данных для добавления элементов (система выдаст ошибку MethodError: no method matching push!).

pop!

Удаляет и возвращает последний элемент из одномерного массива (вектора). Применимо только к изменяемым (динамическим) массивам.

v = [1, 2, 3, 4]
last_element = pop!(v)

m = reshape(1:6, 2, 3)
# pop! нельзя применить к матрице или многомерному массиву.

здесь:

  • v = [1, 2, 3, 4] — создает вектор [1, 2, 3, 4].

  • last_element = pop!(v) — удаляет последний элемент вектора (4) и возвращает его. После операции v становится [1, 2, 3], а переменная last_element равна 4.

  • pop! нельзя применить к матрице m или многомерному массиву, так как их размер фиксирован (система выдаст ошибку MethodError: no method matching pop!).

insert!

Вставляет элемент в указанную позицию одномерного массива (вектора). Применимо только к изменяемым (динамическим) массивам.

v = [1, 2, 4, 5]
insert!(v, 3, 3)

m = reshape(1:6, 2, 3)
# insert! нельзя применить к матрице или многомерному массиву.

здесь:

  • v = [1, 2, 4, 5] — создает вектор [1, 2, 4, 5].

  • insert!(v, 3, 3) — вставляет элемент 3 в позицию с индексом 3. После операции v становится [1, 2, 3, 4, 5].

  • insert! нельзя применить к матрице m или многомерному массиву, так как их структура фиксирована и не поддерживает динамическое изменение.

deleteat!

Удаляет элемент по указанному индексу из одномерного массива (вектора). Применимо только к изменяемым (динамическим) массивам.

v = [1, 2, 3, 4, 5]
deleteat!(v, 3)

m = reshape(1:6, 2, 3)
# deleteat! нельзя применить к матрице или многомерному массиву.

здесь:

  • v = [1, 2, 3, 4, 5] — создает вектор [1, 2, 3, 4, 5].

  • deleteat!(v, 3) — удаляет элемент с индексом 3 (значение 3) из вектора. После операции v становится [1, 2, 4, 5].

  • deleteat! нельзя применить к матрице m или многомерному массиву, так как их структура фиксирована и не поддерживает удаление элементов (система выдаст ошибку MethodError: no method matching deleteat!).

Работа с размерами
Работа с размерами

Имя функции

Описание

Пример

size

Возвращает размер массива, матрицы или вектора в виде кортежа, где каждое значение соответствует размеру по конкретному измерению.

v = [1, 2, 3]
size(v)

m = reshape(1:6, 2, 3)
size(m)

a = zeros(2, 2, 3)
size(a)

здесь:

  • v = [1, 2, 3] — создает вектор [1, 2, 3].

  • size(v) — возвращает кортеж (3,), где 3 — длина вектора.

  • m = reshape(1:6, 2, 3) — создает матрицу 2x3:

    2×3 reshape(::UnitRange{Int64}, 2, 3) with eltype Int64:
     1  3  5
     2  4  6
  • size(m) — возвращает кортеж (2, 3), где 2 — количество строк, а 3 — количество столбцов.

  • a = zeros(2, 2, 3) — создает трехмерный массив размером 2x2x3, заполненный нулями.

  • size(a) — возвращает кортеж (2, 2, 3), соответствующий размерам массива по каждому измерению.

resize!

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

v = [1, 2, 3]
resize!(v, 5)
resize!(v, 2)

m = reshape(1:6, 2, 3)
# resize! нельзя применить к матрице или многомерному массиву.

здесь:

  • v = [1, 2, 3] — создает вектор [1, 2, 3].

  • resize!(v, 5) — увеличивает размер вектора до 5, добавляя элементы со значением 0.0. После операции v становится [1, 2, 3, 0.0, 0.0].

  • resize!(v, 2) — уменьшает размер вектора до 2. После операции v становится [1, 2].

  • resize! нельзя применить к матрице m или многомерному массиву, так как их структура фиксирована (система выдаст ошибку MethodError: no method matching resize!).

length

Возвращает общее количество элементов в массиве, векторе или матрице вне зависимости от их размерности.

v = [1, 2, 3]
length(v)

m = reshape(1:6, 2, 3)
length(m)

a = zeros(2, 2, 3)
length(a)

здесь:

  • v = [1, 2, 3] — создает вектор [1, 2, 3].

  • length(v) — возвращает 3, так как вектор содержит три элемента.

  • m = reshape(1:6, 2, 3) — создает матрицу 2x3:

    2×3 reshape(::UnitRange{Int64}, 2, 3) with eltype Int64:
     1  3  5
     2  4  6
  • length(m) — возвращает 6, так как матрица содержит 6 элементов (произведение строк и столбцов).

  • a = zeros(2, 2, 3) — создает трехмерный массив размером 2x2x3.

  • length(a) — возвращает 12, так как массив содержит 12 элементов (произведение размеров по всем измерениям).

ndims

Возвращает количество измерений (размерностей) массива, вектора или матрицы.

v = [1, 2, 3]
ndims(v)

m = reshape(1:6, 2, 3)
ndims(m)

a = zeros(2, 2, 3)
ndims(a)

здесь:

  • v = [1, 2, 3] — создает вектор [1, 2, 3].

  • ndims(v) — возвращает 1, так как вектор имеет одно измерение.

  • m = reshape(1:6, 2, 3) — создает матрицу 2x3:

    2×3 reshape(::UnitRange{Int64}, 2, 3) with eltype Int64:
     1  3  5
     2  4  6
  • ndims(m) — возвращает 2, так как матрица имеет два измерения (строки и столбцы).

  • a = zeros(2, 2, 3) — создает трехмерный массив размером 2x2x3.

  • ndims(a) — возвращает 3, так как массив имеет три измерения.

reshape

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

v = [1, 2, 3, 4, 5, 6]
m = reshape(v, 2, 3)

a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
b = reshape(a, 3, 3)

c = reshape(a, 1, 9)

здесь:

  • v = [1, 2, 3, 4, 5, 6] — создает вектор из 6 элементов.

  • reshape(v, 2, 3) — преобразует вектор в матрицу размером 2x3, содержащую элементы:

    2×3 Matrix{Int64}:
     1  3  5
     2  4  6
  • a = [1, 2, 3, 4, 5, 6, 7, 8, 9] — создает вектор из 9 элементов.

  • reshape(a, 3, 3) — преобразует вектор в матрицу 3x3:

    3×3 Matrix{Int64}:
     1  4  7
     2  5  8
     3  6  9
  • reshape(a, 1, 9) — преобразует вектор в строку длиной 9 элементов:

    1×9 Matrix{Int64}:
     1  2  3  4  5  6  7  8  9
Линейная алгебра
Линейная алгебра

Имя функции

Описание

Пример

transpose

Возвращает транспонированный массив или матрицу, где строки меняются местами со столбцами. Для одномерного массива (вектора) результат зависит от его ориентации.

v = [1, 2, 3]
vt = transpose(v)

m = [1 2; 3 4; 5 6]
mt = transpose(m)

здесь:

  • v = [1, 2, 3] — создает столбцовый вектор [1 2 3].

  • transpose(v) — транспонирует вектор, превращая его в строковый вектор:

    1×3 transpose(::Vector{Int64}) with eltype Int64:
     1  2  3
  • m = [1 2; 3 4; 5 6] — создает матрицу 3x2:

    3×2 Matrix{Int64}:
     1  2
     3  4
     5  6
  • transpose(m) — транспонирует матрицу, превращая ее в матрицу 2x3:

    2×3 transpose(::Matrix{Int64}) with eltype Int64:
     1  3  5
     2  4  6

*det

Вычисляет детерминант квадратной матрицы. Является частью пакета LinearAlgebra (требует установки пакета).

Детерминант — это скалярная величина, характеризующая свойства матрицы (например, ее обратимость).

m1 = [2 3; 1 4]
det(m1)

m2 = [1 2 3; 4 5 6; 7 8 10]
det(m2)

m3 = [0 1; 2 3]
det(m3)

здесь:

  • m1 = [2 3; 1 4] — создает матрицу 2x2:

    2×2 Matrix{Int64}:
     2  3
     1  4
  • det(m1) — возвращает 5.0, так как детерминант вычисляется по формуле для 2×2 матриц ((2*4)-(3*1) = 8-3 = 5).

  • m2 = [1 2 3; 4 5 6; 7 8 10] — создает матрицу 3x3:

    3×3 Matrix{Int64}:
     1  2   3
     4  5   6
     7  8  10
  • det(m2) — возвращает -3.0, так как вычисляется через разложение Лапласа или другие алгоритмы для матриц большего размера.

  • m3 = [0 1; 2 3] — создает матрицу 2x2:

    2×2 Matrix{Int64}:
     0  1
     2  3
  • det(m3) — возвращает -2.0, так как детерминант равен: (0*3) - (1*2) = 0-2 = -2).

inv

Вычисляет обратную матрицу для квадратной матрицы. Обратная матрица существует только для матриц с ненулевым детерминантом, то есть для тех, которые обратимы.

Функция inv возвращает обратную матрицу, которая имеет тип Matrix{Float64}, даже если исходная матрица имеет целочисленные значения (Matrix{Int64}).

m1 = [2 3; 1 4]
inv(m1)

m2 = [1 2 3; 0 1 4; 5 6 0]
inv(m2)

m3 = [1 2; 3 4]
inv(m3)

здесь:

  • m1 = [2 3; 1 4] — создает матрицу 2x2:

    2×2 Matrix{Int64}:
     2  3
     1  4
  • inv(m1) — возвращает обратную матрицу:

    2×2 Matrix{Float64}:
      0.8  -0.6
     -0.2   0.4
  • m2 = [1 2 3; 0 1 4; 5 6 0] — создает матрицу 3x3:

    3×3 Matrix{Int64}:
     1  2  3
     0  1  4
     5  6  0
  • inv(m2) — возвращает обратную матрицу:

    3×3 Matrix{Float64}:
     -24.0   18.0   5.0
      20.0  -15.0  -4.0
      -5.0    4.0   1.0
  • m3 = [1 2; 3 4] — создает матрицу 2x2:

    2×2 Matrix{Int64}:
     1  2
     3  4
  • inv(m3) — возвращает обратную матрицу:

    2×2 Matrix{Float64}:
     -2.0   1.0
      1.5  -0.5

*eigvals / *eigvecs

Вычисление собственных значений и векторов матрицы. Например:

  • eigvals(m) — возвращает собственные значения матрицы m.

  • eigvecs(m) — возвращает собственные векторы матрицы m.

m1 = [4 1; 2 3]
eigvals(m1)
eigvecs(m1)

m2 = [1 2 3; 4 5 6; 7 8 9]
eigvals(m2)
eigvecs(m2)

здесь:

  • m1 = [4 1; 2 3] — создает матрицу 2x2:

    2×2 Matrix{Int64}:
     4  1
     2  3
  • eigvals(m1) — возвращает собственные значения:

    2-element Vector{Float64}:
     2.0
     5.0
  • eigvecs(m1) — возвращает собственные векторы:

    2×2 Matrix{Float64}:
     -0.447214  0.707107
      0.894427  0.707107
  • m2 = [1 2 3; 4 5 6; 7 8 9] — создает матрицу 3x3:

    3×3 Matrix{Int64}:
     1  2  3
     4  5  6
     7  8  9
  • eigvals(m2) — возвращает собственные значения:

    3-element Vector{Float64}:
     -1.1168439698070416
     -9.759184829871139e-16
     16.116843969807043
  • eigvecs(m2) — возвращает собственные векторы:

    3×3 Matrix{Float64}:
     -0.78583     0.408248  -0.231971
     -0.0867513  -0.816497  -0.525322
      0.612328    0.408248  -0.818673

norm

Вычисляет норму вектора или матрицы.

Норма — это числовая характеристика, которая измеряет «размер» вектора или матрицы. По умолчанию вычисляется Евклидова норма.

v = [3, 4]
norm(v)

m = [1 2; 3 4]
norm(m)

norm(v, 1) # 3 + 4 = 7.0
norm(m, Inf) #√30 ≈ 5.477

здесь:

  • norm(v) — вычисляет Евклидову норму для вектора.

  • norm(v, 1) — вычисляет норму 1-го порядка (сумму модулей элементов).

  • norm(m) — вычисляет Евклидову норму для матрицы (норма Фробениуса).

  • norm(m, Inf) — вычисляет норму бесконечности (максимальную строковую сумму). В примере = 4.0.

Проверка
Проверка

Имя функции

Описание

Пример

isempty

Проверяет, является ли массив, вектор или матрица пустым. Возвращает true, если объект пустой, и false в противном случае.

# Проверка пустого и непустого вектора
v1 = []
v2 = [1, 2, 3]
isempty(v1)
isempty(v2)

# Проверка пустой и непустой матрицы
m1 = reshape([], 0, 0)
m2 = [1 2; 3 4]
isempty(m1)
isempty(m2)

# Проверка пустого и непустого массива
a1 = Int[]
a2 = [1, 2, 3, 4]
isempty(a1
isempty(a2)

здесь:

  • Для вектора:

    • isempty(v1) возвращает true, так как v1 не содержит элементов.

    • isempty(v2) возвращает false, так как вектор v2 содержит три элемента [1, 2, 3].

  • Для матрицы:

    • isempty(m1) возвращает true, так как матрица m1 имеет размер 0×0.

    • isempty(m2) возвращает false, так как матрица m2 имеет размер 2×2.

  • Для массива:

    • isempty(a1) возвращает true, так как массив a1 пуст.

    • isempty(a2) возвращает false, так как массив a2 содержит четыре элемента.

iszero

Проверяет, является ли объект нулевым. Возвращает true, если объект равен нулю, и false в противном случае.

Поддерживает числовые типы, а также массивы, матрицы и векторы (в Julia объект считается нулевым, если все его элементы равны нулю).

# Проверка числа
x = 0
y = 5
iszero(x)  # true
iszero(y)  # false

# Проверка вектора
v1 = [0, 0, 0]
v2 = [0, 1, 0]
iszero(v1)  # true
iszero(v2)  # false

# Проверка матрицы
m1 = zeros(2, 2)нулями
m2 = [1 0; 0 0]элементом
iszero(m1)  # true
iszero(m2)  # false

# Проверка многомерного массива
a1 = zeros(2, 2, 2)
a2 = reshape([1, 0, 0], 1, 3)
iszero(a1)  # true
iszero(a2)  # false

здесь:

Для чисел:

  • iszero(x) возвращает true, так как x = 0.

  • iszero(y) возвращает false, так как y = 5.

Для векторов:

  • iszero(v1) возвращает true, так как все элементы вектора v1 равны 0.

  • iszero(v2) возвращает false, так как второй элемент вектора v2 равен 1.

Для матриц:

  • iszero(m1) возвращает true, так как все элементы матрицы m1 равны 0.

  • iszero(m2) возвращает false, так как матрица m2 содержит ненулевой элемент 1.

Для многомерных массивов:

  • iszero(a1) возвращает true, так как все элементы массива a1 равны 0.

  • iszero(a2) возвращает false, так как первый элемент массива a2 равен 1.

issymmetric

Проверяет, является ли матрица симметричной. Возвращает true, если матрица равна своей транспонированной копии, и false в противном случае. Работает только с квадратными матрицами; для других форматов возвращает false.

# Симметричная матрица
m1 = [1 2 3; 2 4 5; 3 5 6]
issymmetric(m1)  # true

# Несимметричная матрица
m2 = [1 0 0; 0 4 5; 3 5 6]
issymmetric(m2)  # false

# Квадратная, но несимметричная матрица
m3 = [1 2; 3 4]
issymmetric(m3)  # false

# Несимметричная прямоугольная матрица
m4 = [1 2; 2 3; 3 4]
issymmetric(m4)  # false

здесь:

  • Для матрицы m1:

    Матрица m1 равна своей транспонированной копии:

    [1 2 3]       [1 2 3]
    [2 4 5]  ->   [2 4 5]
    [3 5 6]       [3 5 6]

    issymmetric(m1) возвращает true.

  • Для матрицы m2:

    Матрица m2 не равна своей транспонированной копии:

    [1 0 0]       [1 0 3]
    [0 4 5]  ->   [0 4 5]
    [3 5 6]       [0 5 6]

    issymmetric(m2) возвращает false.

  • Для матрицы m3:

    Матрица m3 квадратная, но несимметричная:

    [1 2]       [1 3]
    [3 4]  ->   [2 4]

    issymmetric(m3) возвращает false.

  • Для матрицы m4:

    Матрица m4 прямоугольная (не квадратная), поэтому issymmetric(m4) возвращает false.

Преобразование
Преобразование

Имя функции

Описание

Пример

vec

Преобразует матрицу или многомерный массив в вектор. Она возвращает одномерный массив, в котором элементы берутся по столбцам исходной матрицы или массива. Если аргумент уже является вектором, то он возвращается без изменений.

# Преобразование матрицы в вектор
m = [1 2; 3 4]
v1 = vec(m)  # [1, 3, 2, 4]

# Преобразование трехмерного массива в вектор
a = zeros(2, 2, 2)
a[:, :, 1] .= [1 2; 3 4]
a[:, :, 2] .= [5 6; 7 8]
v2 = vec(a)  # [1, 3, 2, 4, 5, 7, 6, 8]

# Преобразование вектора (без изменений)
v = [1, 2, 3]
v3 = vec(v)  # [1, 2, 3]
  • Матрица m имеет вид:

    2×2 Matrix{Int64}:
     1  2
     3  4

    Функция vec преобразует ее в вектор, последовательно извлекая элементы из каждого столбца:

    [1, 3, 2, 4]
  • Для трехмерного массива a:

    Исходный массив a выглядит так (по слоям):

    #Слой 1
    [1 2]
    [3 4]
    
    #Слой 2
    [5 6]
    [7 8]

    vec преобразует его в вектор:

    [1, 3, 2, 4, 5, 7, 6, 8]

    Для вектора v:

    Если объект уже является вектором, то vec возвращает его без изменений:

    3-element Vector{Int64}:
     1
     2
     3

permutedims

Перестановка измерений (осей) многомерного массива. Позволяет изменить порядок измерений без изменения самих данных, создавая новый массив с переставленными осями.

A = reshape(collect(1:6), 2, 3)  # 2×3 массив
B = permutedims(A, (2, 1))  # Меняем местами оси 1 и 2

здесь:

  • permutedims(A, (2, 1)) — меняет местами строки и столбцы, то есть транспонирует матрицу.

  • Функция создает новый массив, не изменяя A (в отличие от transpose, который работает только с 2D-матрицами).

  • Можно переставлять произвольное количество измерений, например, permutedims(A, (2, 1, 3)) для 3D-массивов.

hcat / vcat

Горизонтальное и вертикальное объединение массивов (Конкатенация).

  • hcat (горизонтальная конкатенация) — объединяет массивы или векторы горизонтально (по столбцам), формируя новую матрицу или массив.

  • vcat (вертикальная конкатенация) — объединяет массивы или векторы вертикально (по строкам), формируя новую матрицу или массив.

# Горизонтальное объединение (hcat)
v1 = [1, 2, 3]
v2 = [4, 5, 6]
h_result = hcat(v1, v2)

# Вертикальное объединение (vcat)
v3 = [7, 8, 9]
v_result = vcat(v1, v3)

# Объединение матриц
m1 = [1 2; 3 4]
m2 = [5 6; 7 8]
hcat_result = hcat(m1, m2)
vcat_result = vcat(m1, m2)

здесь:

  • h_result = hcat(v1, v2) происходит горизонтальное объединение в матрицу 3x2:

    3×2 Matrix{Int64}:
     1  4
     2  5
     3  6
  • v_result = vcat(v1, v3) объединение в вектор:

    6-element Vector{Int64}:
     1
     2
     3
     7
     8
     9
  • hcat_result = hcat(m1, m2) объединяет в матрицу 2x4:

    2×4 Matrix{Int64}:
     1  2  5  6
     3  4  7  8
  • vcat_result = vcat(m1, m2) объединяет в матрицу 4x2:

    4×2 Matrix{Int64}:
     1  2
     3  4
     5  6
     7  8

copy

создает независимую копию массива, вектора или матрицы. Изменения, внесенные в копию, не влияют на оригинальный объект и наоборот.

v = [1, 2, 3]
v_copy = copy(v)
v[1] = 10  # Изменяем оригинал
println(v)       # [10, 2, 3]
println(v_copy)  # [1, 2, 3]

здесь:

  • изменение элемента в оригинале (v[1] = 10) не влияет на копию v_copy.

deepcopy

создает глубокую копию массива, рекурсивно копируя все вложенные структуры. Изменения в копии не влияют на оригинал. В Julia присваивание по умолчанию передает не значение, а ссылку на объект. Если нужно создать независимую копию, то есть два способа:

  • copy — делает неглубокую копию (новый контейнер, но ссылки на те же элементы).

  • deepcopy — делает глубокую копию (все копируется рекурсивно).

A = [[1, 2], [3, 4]]
B = copy(A)       # Неглубокая копия
C = deepcopy(A)   # Глубокая копия

B[1][1] = 99
println(A)  # [[99, 2], [3, 4]]  — A изменился, потому что B и A связаны
println(C)  # [[1, 2], [3, 4]]  — Глубокая копия не изменилась

здесь:

  • изменение вложенного элемента в оригинале (v[1][1] = 10) не влияет на глубокую копию v_deepcopy.

broadcast

Позволяет выполнять операции над элементами массивов с автоматическим согласованием размеров (broadcasting). Подробнее см. здесь.

# Пример с вектором
v = [1, 2, 3]
v_squared = broadcast(x -> x^2, v)

# Пример с матрицей
m = [1 2; 3 4]
m_doubled = broadcast(x -> x * 2, m)

# Пример с разными размерами массивов
a = [1, 2, 3]
b = [4, 5]
result = broadcast(+, a, b')

здесь:

  • v_squared = broadcast(x -> x^2, v) образует:

    3-element Vector{Int64}:
     1
     4
     9
  • m_doubled = broadcast(x → x * 2, m) образует:

    2×2 Matrix{Int64}:
     2  4
     6  8
  • result = broadcast(+, a, b') образует:

    3×2 Matrix{Int64}:
     5  6
     6  7
     7  8

    b' — транспонированный вектор.

  • broadcast(x -> x^2, v) — квадраты элементов вектора

Уместность применения массивов

Массивы — это удобный и гибкий инструмент для работы с данными, но в Julia есть и другие структуры, которые могут лучше подойти для определенных задач. Подробнее см. ниже.

Когда рекомендуются массивы

  • Быстрый доступ к элементам по индексу — массивы хранят элементы последовательно в памяти, что позволяет мгновенно получить доступ к любому элементу по его индексу. Например, arr[3] сразу вернет третий элемент массива. Это называется случайным доступом (random access), и он работает за время O(1) (операция выполняется за константное время, независимо от размера входных данных, то бишь доступ к элементу по индексу занимает одинаковое время, независимо от того, насколько большой массив).

  • Эффективное добавление и удаление в конце — если вам нужно часто добавлять или удалять элементы в конце массива, то используйте функции push! (добавить элемент) и pop! (удалить последний элемент). Эти операции выполняются очень быстро, так как не требуют перестановки других элементов.

  • Работа с фиксированным количеством данных — если вы заранее знаете, сколько элементов будет храниться, то массивы работают особенно эффективно. Они выделяют память сразу, что минимизирует накладные расходы на изменение размера.

  • Векторные вычисления и линейная алгебра — Julia оптимизирована для работы с массивами, особенно с числовыми. Если работаете с матрицами, векторами или выполняете операции линейной алгебры, то массивы — это лучший выбор. Например, функции для работы с матрицами, такие как dot, cross, или операции с массивами через broadcast (.+, .*), работают быстрее именно с массивами.

Когда не рекомендуются массивы

  • Частое добавление или удаление элементов в середине или начале — если нужно часто вставлять или удалять элементы в начале или середине массива. Например, функции insert! (вставка) и deleteat! (удаление) требуют сдвига всех последующих элементов, что делает их неэффективными для больших массивов. Альтернатива: используйте Deque из пакета DataStructures.jl, который оптимизирован для вставки и удаления с обоих концов.

  • Поиск элементов по ключу — если нужно быстро находить элементы по уникальным ключам (например, по имени пользователя или ID), то массивы не подходят, ведь в них поиск выполняется перебором всех элементов (что достаточно долго). Альтернатива: используйте Dict (словарь), который позволяет быстро находить элементы по ключу.

  • Частое изменение размера структуры — если структура данных часто меняется (например, элементы добавляются или удаляются в произвольных местах), то массивы будут неэффективны. Альтернатива: рассмотрите использование Set, Dict или списковых структур, таких как List из пакета DataStructures.jl.

  • Работа с неизменяемыми данными — массивы в Julia изменяемые, то есть их элементы можно менять после создания. Если нужна неизменяемая структура данных, то лучше использовать кортежи (Tuple). Они не позволяют изменять свои элементы, что делает их удобными для хранения константных данных. Например:

    t = (1, 2, 3)
    t[1] = 10  # Ошибка! Кортежи неизменяемы

Массивы, матрицы и векторы строк и символов

В Julia массивы, матрицы и векторы могут содержать строки или символы, так же как и числа.

# Вектор строк
v = ["apple", "banana", "cherry"]
println(v[1])  # apple

# Матрица символов
m = ['a' 'b'; 'c' 'd']
println(m[1, 2])  # b

# Трехмерный массив строк
a = [["a", "b"], ["c", "d"]]
println(a[1][2])  # "b"
  • Вектор строк — каждая строка хранится как элемент одномерного массива.

  • Матрица символов — двумерный массив, где каждый элемент — символ.

  • Трехмерный массив строк — поддерживается произвольное количество измерений с текстовыми данными.

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

Более сложные конструкции с end, такие, как end+1, не позволяют напрямую присваивать значение элементу массива, так как такие операции выходят за пределы текущей длины. Чтобы добавить новые элементы, нужно явно увеличить размер массива. Например, можно использовать resize!:

a = [10, 20, 30]
resize!(a, length(a) + 1)  # Расширяем массив на 1 элемент
a[end] = 40  # Присваиваем значение новому элементу
println(a)  # [10, 20, 30, 40]

Конструкции с end доступны для индексации и выполнения арифметических операций с индексами внутри существующих границ массива. Например:

  • a[end] — доступ к последнему элементу.

  • a[end-1] — доступ ко второму с конца элементу.

  • a[1:end] — получение всего массива.

  • a[1:end-1] — получение всех элементов, кроме последнего.

В Julia нельзя напрямую присваивать значение элементу с индексом end+1 или любому индексу за пределами текущей длины массива. Чтобы изменить размер массива, необходимо использовать специальные методы, такие как resize!, push! или append!.

Индексация массивов

Индексация в Julia начинается с единицы, то есть первый элемент массива имеет индекс 1. Также поддерживается специальный индекс end, который обозначает последний элемент массива или размер соответствующего измерения. Пример индексации:

a = [10, 20, 30, 40]
println(a[1])  # 10 — первый элемент
println(a[end])  # 40 — последний элемент

# Индексация матриц
m = [1 2; 3 4]
println(m[1, end])  # 2 — последний элемент первой строки
  • Индекс 1 всегда указывает на первый элемент.

  • end используется для ссылки на последний элемент в одномерных и многомерных массивах.

  • Для матриц первый индекс - номер строки, второй - номер столбца.

Другие вариации индексации:

  • Можно обратиться сразу к нескольким элементам массива с использованием диапазонов:

    b = [1, 2, 3, 4, 5]
    println(b[2:end])  # [2, 3, 4, 5] — элементы со второго до последнего
  • Выбор элементов с определенным шагом:

    b = [1, 2, 3, 4, 5]
    println(b[1:2:end])  # [1, 3, 5] — элементы с шагом 2

    В Julia выражения вида 1:2:end называют диапазонами (range). Диапазон — это структура, которая описывает последовательность чисел с началом, шагом и концом. Например, 1:2:5 создает последовательность от 1 до 5 с шагом 2: [1, 3, 5].

    В данном примере 1:2:end указывает, что выбор начинается с первого элемента массива (1), идет с шагом 2 и заканчивается на последнем элементе массива (end). Диапазоны используются для удобной и эффективной работы с индексами массивов и матриц. Примеры использования диапазонов:

    # Простые диапазоны
    r1 = 1:5           # Создает последовательность [1, 2, 3, 4, 5]
    r2 = 1:2:10        # Создает последовательность [1, 3, 5, 7, 9]
    
    # Использование диапазона для индексации
    a = [10, 20, 30, 40, 50]
    println(a[2:4])    # [20, 30, 40]
    println(a[1:2:end]) # [10, 30, 50]
    
    # Диапазон с "end"
    println(a[3:end])  # [30, 40, 50]
  • Выбор элементов, соответствующих определенным условиям (подробнее см. ниже):

    b = [1, 2, 3, 4, 5]
    println(b[b .> 3])  # [4, 5] — элементы больше 3

В Julia диапазоны можно задавать не только напрямую, но и с использованием переменных. Например:

arr = [10, 20, 30, 40, 50]
a = 1:2:5         # Задаем диапазон в переменной
println(arr[a])   # [10, 30, 50]

Логическая индексация

Логическая индексация в Julia позволяет выбирать элементы массива, основываясь на условиях, которые возвращают логический массив (true/false) той же размерности, что и исходный массив. Этот метод удобен для фильтрации данных. Пример:

# Исходный массив
a = [10, 20, 30, 40, 50]

# Условие: выбрать элементы больше 25
filtered = a[a .> 25]  # [30, 40, 50]
println(filtered)

# Условие: элементы, которые кратны 10
filtered = a[a .% 10 .== 0]  # [10, 20, 30, 40, 50]
println(filtered)

здесь:

  • оператор . перед условием (.>, .==) означает поэлементную операцию. Более подробное описание таких операций представлено в статье.

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

Пример с многомерным массивом:

# Матрица 2x3
m = [1 2 3; 4 5 6]

# Условие: выбрать элементы больше 3
filtered = m[m .> 3]  # [4, 5, 6]
println(filtered)

Операторы для работы с массивами, векторами и матрицами

Julia предоставляет операторы, упрощающие работу с массивами — будь то одномерные (векторы), двумерные (матрицы) или более сложные многомерные структуры. Вот основные операторы, которые используются для индексации, трансформации и объединения данных:

  • Оператор : используется для выбора всех элементов массива вдоль определенного измерения или для создания диапазонов. Пример:

    # Исходный массив
    m = [1 2 3; 4 5 6; 7 8 9]
    
    # Все элементы первой строки
    println(m[1, :])  # [1, 2, 3]
    
    # Все элементы второго столбца
    println(m[:, 2])  # [2, 5, 8]
    
    # Диапазон индексов
    println(m[1:2, 1:2])  # [1 2; 4 5]
  • Оператор ' применяется для сопряжения (транспонирования для вещественных чисел). Работает только с числовыми массивами. Пример:

    # Вектор-строка
    v = [1 2 3]
    
    # Транспонирование в столбец
    println(v')  # [1; 2; 3;;]
    Для транспонирования массивов с комплексными числами оператор ' также возвращает комплексно-сопряженный результат. Если нужен только перевод строк в столбцы (без сопряжения), то используйте функцию transpose.

Для транспонирования массивов с нечисловыми элементами (например, строки или пользовательские типы) используйте функцию permutedims. В отличие от transpose, которая предназначена для числовых данных, permutedims работает с любыми типами.

Если применить оператор ' к нечисловому массиву, то это вызовет ошибку. В Julia оператор ' вызывает adjoint (сопряженное транспонирование), который предназначен для работы с числовыми и комплексными массивами. Для строк и других объектов он не определен и приведет к ошибке MethodError. Например:

arr = ["a" "b"; "c" "d"] # Матрица строк (2×2)
arr_transposed = permutedims(arr, (2, 1)) # Меняем строки и столбцы местами
arr_wrong = arr' # Ошибка!
  • Оператор ; используется для объединения массивов по вертикали, то есть добавления строк. Пример:

    # Исходные векторы
    a = [1, 2, 3]
    b = [4, 5, 6]
    
    # Вертикальная конкатенация
    result = [a; b]
    println(result)  # 6-element Vector{Int64}:[1; 2; 3; 4; 5; 6]

Итерация по массивам с помощью for

В Julia массивы можно удобно перебирать с помощью цикла for. Рассмотрим основные способы работы с массивами через for. Для перебора всех элементов массива используется следующий синтаксис:

arr = [10, 20, 30, 40]

for x in arr
    println(x)
end
Вывод
10
20
30
40

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

arr = ["a", "b", "c"]

for i in eachindex(arr)
    println("Элемент ", i, ": ", arr[i])
end
Вывод
Элемент 1: a
Элемент 2: b
Элемент 3: c

Альтернативный способ получить индекс и значение — функция enumerate:

arr = ["apple", "banana", "cherry"]

for (i, val) in enumerate(arr)
    println("$i: $val")
end
Вывод
1: apple
2: banana
3: cherry

Для многомерных массивов можно использовать вложенные циклы:

matrix = [1 2 3; 4 5 6]

for i in 1:size(matrix, 1)  # Проход по строкам
    for j in 1:size(matrix, 2)  # Проход по столбцам
        println("matrix[$i, $j] = ", matrix[i, j])
    end
end
Вывод
matrix[1, 1] = 1
matrix[1, 2] = 2
matrix[1, 3] = 3
matrix[2, 1] = 4
matrix[2, 2] = 5
matrix[2, 3] = 6

Вместо вложенных циклов можно использовать for напрямую:

matrix = [1 2 3; 4 5 6]

for x in matrix
    println(x)
end
Вывод
1
4
2
5
3
6

Julia перебирает элементы по столбцам, а не по строкам. Чтобы обойти массив по строкам, можно использовать permutedims или eachrow:

for row in eachrow(matrix)
    println(row)
end
Вывод
[1, 2, 3]
[4, 5, 6]

1. Макросы в Julia — это специальные конструкции, начинающиеся с символа @, которые преобразуют код перед его выполнением.