Синтаксис языка Julia
Функции
Создание функций
В языке Julia функция представляет собой объект, который сопоставляет кортеж значений аргументов с возвращаемым значением. Базовый синтаксис определения функций в Julia следующий:
function f(x,y)
x + y
end
Эта функция принимает два аргумента, x и y, и возвращает значение последнего вычисленного выражения: x + y.
Есть и второй, более сжатый синтаксис для определения функции в Julia. Традиционный синтаксис объявления функций, показанный выше, эквивалентен следующей компактной форме присваивания:
f(x,y) = x + y
В форме присваивания тело функции должно быть одним выражением. Короткий синтаксис функций довольно наглядный, он значительно сокращает объем набираемого текста и устраняет визуальный шум.
Вызывается функция всегда с помощью употребления ее имени вместе со списком значений параметров, взятым в круглые скобки:
f(2,3)
✏️Сделать примером
Создайте функцию f(x), вычисляющую значение многочлена . Затем вызовите функцию f со значениями аргумента, равными 4 и 7.
Решение
function f(x)
2*x^2+3*x+5
end
f(4)
f(7)
Без скобок выражение f ссылается на функцию как объект и может передаваться, как и любое другое значение:
g = f
g(2,3)
Как и в случае с другими именами в Julia, в качестве имен функций могут быть использованы любые символы Unicode.
Объявления типов аргументов
Вы можете объявлять типы аргументов функций, добавляя ::TypeName к имени аргумента. Например, следующая функция рекурсивно вычисляет числа Фибоначчи:
fib(n::Integer) = n ≤ 2 ? one(n) : fib(n-1) + fib(n-2)
Здесь спецификация ::Integer означает, что функция fib будет вызываемой, только если ее аргумент n является подтипом абстрактного типа Integer.
Следующие вызовы функции fib возвращают значения элементов последовательности Фибоначчи с номерами 3 и 10:
fib(3)
fib(10)
Если мы попытаемся в качестве аргумента задать нецелое число, то получим сообщение об ошибке:
fib(2.5)
MethodError: no method matching fib(::Float64)
Closest candidates are:
fib(::Integer)
@ Main In[8]:1
Stacktrace:
[1] top-level scope
@ In[13]:1
Объявления типов могут быть полезными, если ваша функция возвращает правильные результаты только для определенных типов аргументов. Например, если бы мы опустили типы аргументов и написали
fib(n) = n ≤ 2 ? one(n) : fib(n-1) + fib(n-2),
то вызов fib(2.5) дал бы нам бессмысленный ответ 2.0.
Ключевое слово return
Значение, возвращаемое функцией, – это значение последнего вычисленного выражения, которое по умолчанию является последним выражением в теле определения функции. В примере функции f из
раздела Создание функций это значение выражения x + y. В качестве альтернативы ключевое слово return заставляет функцию немедленно выполнить возврат, предоставляя выражение, значение которого возвращается.
Например:
function g(x,y)
return x * y
x + y
end
Вызовем функцию g с аргументами 2 и 3:
g(2,3)
Как мы видим, функция g выполнила умножение аргументов 2 и 3, т.е. то действие, которое записано сразу после ключевого слова return, а следующее действие (сложение аргументов x и y) было проигнорировано.
Конечно, в истинно линейном теле функции, например g, использование return бессмысленно. Так как выражение x + y никогда не вычисляется, мы могли бы просто сделать x * y последним выражением в функции и опустить return. Однако в случае другого потока управления return действительно может быть полезен.
Вот, например, функция quadratic, которая вычисляет корни квадратного уравнения . Аргументами функции являются коэффициенты a, b и c.
function quadratic(a,b,c)
D = b^2 - 4*a*c
if D < 0
return "действительных корней нет"
elseif D == 0
return -b/2a
else
return ((-b+sqrt(D))/2a, (-b-sqrt(D))/2a)
end
end
Есть три возможные точки возврата из этой функции, возвращающие значения трех разных выражений, в зависимости от значения дискриминанта D. Ключевое слово return в последней строке можно было бы опустить, так как это последнее выражение.
Анонимные функции
Функции в Julia являются объектами первого класса: их можно присваивать переменным и вызывать с помощью стандартного синтаксиса вызова функции из переменной, которой они были присвоены. Они могут использоваться как аргументы и возвращаться как значения. Их также можно создавать анонимно, без присваивания им имени, с помощью любого из этих синтаксисов:
x -> x^2 + 2x - 1
function (x)
x^2 + 2x - 1
end
Этот код создает функцию, принимающую один аргумент x и возвращающую значение многочлена x^2 + 2x - 1 от этой величины. Обратите внимание, что результат представляет собой универсальную функцию, но с именем, сгенерированным компилятором на основе последовательной нумерации.
Основное использование анонимных функций – их передача в функции, которые принимают другие функции в качестве аргументов. Классический пример – функция map, которая применяет функцию к каждому значению массива и возвращает новый массив, содержащий результирующие значения:
map(sin, [1.2, 3.5, 1.7])
Это отлично работает, если уже существует осуществляющая преобразование именованная функция для передачи в качестве первого аргумента в map. Однако часто готовой к использованию именованной функции не существует. В этих ситуациях конструкция анонимной функции позволяет легко создавать объект функции для однократного использования без указания имени:
map(x -> x^2 + 2x - 1, [1, 3, -1])
Анонимную функцию, принимающую несколько аргументов, можно записать с помощью следующего синтаксиса:
(x,y,z) -> 2x + y - z
Кортежи
В Julia имеется встроенная структура данных, называемая кортежем, которая тесно связана с аргументами функций и возвращаемыми значениями. Кортеж – это контейнер фиксированной длины, который может содержать любые значения, но не может быть изменен. Кортежи конструируются так: элементы кортежа перечисляются через запятую, и весь этот перечень берется в круглые скобки.
(1, 2, 3)
x = (0.0, "hello", 6*7)
К элементам кортежа можно обращаться с помощью индексирования:
x[2]
Кортеж из одного элемента должен записываться с запятой: (1,), а () представляет собой пустой кортеж (с длиной 0).
Элементам кортежа можно задавать имена, в этом случае конструируется именованный кортеж:
x = (a=1, b=2)
x[1]
К элементам именованного кортежа можно также обращаться по имени с помощью синтаксиса через точку (x.a) помимо обычного синтаксиса индексирования (x[1]).
x.a
Разделенный запятыми список переменных (которые, как вариант, могут заключаться в скобки) может отображаться в левой части присваивания: значение с правой стороны деструктуризируется путем итерации по переменным и присваивания значения каждой из них по очереди:
a, b, c = (1, 2, 3)
b
Значение справа может быть кортежем, массивом или итератором (например, 1:3) как минимум той же длины, что и набор переменных слева (любые излишние элементы в правой части игнорируются).
Кортежи используются для возврата нескольких значений из функции. Например, следующая функция возвращает два значения:
function func(a,b)
a+b, a*b
end
Если вы вызовете ее без присваивания возвращаемого значения какой-либо переменной, то вы увидите, что возвращен кортеж:
func(2,3)
Деструктуризирующее присваивание извлекает каждое значение в переменную:
(x, y) = func(2,3)
Список переменных x и y можно записать и без скобок:
x, y = func(2,3)
println(x)
println(y)
Именованные аргументы
Некоторым функциям требуется большое число аргументов, или у них большое число вариантов поведения. Бывает трудно запомнить, как вызывать такие функции. Именованные аргументы могут сделать эти сложные интерфейсы более простыми в использовании и расширить их, разрешив определение аргументов по имени, а не только по положению.
Например, рассмотрим функцию plot, которая строит линейный график. У этой функции может быть множество параметров, управляющих стилем линии, толщиной, цветом и т.д. Если она принимает именованные аргументы, возможный вызов может выглядеть как plot(x, y, width=2), в котором мы решили указать только толщину линии. Заметьте, что это служит двум целям. Вызов легче читается, так как мы можем пометить аргумент его значением. Также становится возможным передавать любое подмножество большого числа аргументов в любом порядке.
Функции с именованными аргументами определяются с помощью точки с запятой в списке аргументов:
function plot(x, y; style="solid", width=1, color="black")
###
end
При вызове функции точка с запятой необязательна: можно вызвать plot(x, y, width=2) или plot(x, y; width=2), но первый стиль более распространен.
Значения по умолчанию именованных аргументов вычисляются только при необходимости (когда соответствующий именованный аргумент не передан) и в порядке слева направо. Следовательно, выражения по умолчанию могут ссылаться на предыдущие именованные аргументы.
Типы именованных аргументов можно сделать явными следующим образом:
function f(;x::Int=1)
###
end
✏️Сделать примером
Создайте функцию cost, вычисляющую стоимость слитка золота, имеющего форму прямоугольного параллелепипеда. На вход функции должны подаваться 4 именованных аргумента: length (длина, см), width (ширина, см), height (высота, см) и price (цена, руб. за грамм). Плотность золота примите равной . Затем, вызвав эту функцию, вычислите стоимость слитка золота размерами см и ценой 6200 руб. за грамм.
Подсказка
Т.к. все аргументы функции – именованные, то заголовок описания функции будет выглядеть так (список именованных аргументов располагается после точки с запятой):
function cost(; length, width, height, price)
Напомним, что объем слитка () рассчитывается как произведение длины, ширины и высоты, а масса (г) – как произведение объема () на плотность (). Cтоимость равна произведению массы (г) на цену (руб./г). Полученное значение стоимости нужно округлить до большего целого числа (с помощью функции ceil) и затем преобразовать в целый тип (с помощью функции Int) для предотвращения вывода результата в экспоненциальной форме, которая не используется для денежных сумм.
Решение
function cost(; length, width, height, price)
V = length * width * height
m = V * 19.32
return cost = Int(ceil(m * price))
end
cost(length=10, width=3, height=1, price=6200)
Если именованному аргументу не присвоено значение по умолчанию в определении метода, тогда он является обязательным:
function f(x; y)
###
end
f(3, y=5)
Если вызывающий объект не присваивает значение именованному арументу, то будет выдано исключение UndefKeywordError:
f(3)
UndefKeywordError: keyword argument `y` not assigned
Stacktrace:
[1] f(x::Int64)
@ Main ./In[7]:1
[2] top-level scope
@ In[11]:1
Можно также передавать выражения key => value после точки с запятой. Например:
plot(x, y; :width => 2)
эквивалентно
plot(x, y, width=2)
Математические функции
В Julia существует большое количество стандартных математических функций. Рассмотрим наиболее часто используемые из них.
Абсолютные значения и корни
abs(x)– абсолютное значение числаx.abs2(x)– квадрат числаx.sqrt(x)– квадратный корень из числаx.isqrt(x)– целочисленный квадратный корень из целого числаx.cbrt(x)– кубический корень из числаx.
Примеры:
abs(-10)
abs2(-10)
sqrt(1000)
cbrt(729)
Cтепени и логарифмы
exp(x)–ев степениx.exp2(x)– 2 в степениx.exp10(x)– 10 в степениx.expm1(x)–eв степениx – 1(точно).ldexp(x,n)– (nдолжно быть типаFloat).exponent(x)– наибольшее целое числоy, такое, что .log(x)– натуральный логарифмx.log2(x)– логарифмxпо основанию 2.log10(x)– десятичный логарифмx.log(n,x)– логарифмxпо основаниюn.log1p(x)– натуральный логарифм1 + x(точно).significand(x)– двоичная значащая часть (мантисса) числа с плавающей запятойx.
Примеры:
exp(3)
exp2(10)
exp10(6)
exponent(17)
log(234)
log2(1024)
log(3,81)
Тригонометрические функции
Если аргумент x задан в радианах, то
sin(x)– синусx.sinpi(x)– синус .cos(x)– косинусx.cospi(x)– косинус .tan(x)– тангенсx.cot(x)– котангенсx.sec(x)– секансx.csc(x)– косекансx.asin(x)– арксинусx.acos(x)– арккосинусx.atan(x)– арктангенсx.acot(x)– арккотангенсx.asec(x)– арксекансx.acsc(x)– арккосекансx.sinc(x)– кардинальный синусx:cosc(x)– производная от кардинального синусаx:
Для вычисления тригонометрических функций с использованием градусов (а не радиан) добавьте к имени функции окончание d: sind(x), cosd(x), tand(x), cotd(x), secd(x), cscd(x), asind(x), acosd(x), atand(x), acotd(x), asecd(x), acscd(x).
rad2deg(x)– преобразовать уголxиз радиан в градусы.deg2rad(x)– преобразовать уголxиз градусов в радианы.
Примеры:
sin(2.31)
cos(pi)
tan(pi/4)
atan(1)
sinc(0.5)
rad2deg(atan(1))
deg2rad(180)
Гиперболические функции
sinh(x)– гиперболический синусx.cosh(x)– гиперболический косинусx.tanh(x)– гиперболический тангенсx.coth(x)– гиперболический котангенсx.sech(x)– гиперболический секансx.csch(x)– гиперболический косекансx.
Соответствующие им обратные гиперболические функции: asinh(x), asoch(x), atanh(x), acoth(x), asech(x), acsch(x).
Примеры:
sinh(2)
cosh(0)
tanh(1)
Комбинаторные функции
factorial(x)– факториал числаx.binomial(x,y)– число сочетаний изaпоb(биномиальный коэффициент).
Примеры:
factorial(10)
binomial(6,4)
Функции округления
-
round(x)– округляетxдо ближайшего целого числа. -
ceil(x)– округляетxдо ближайшего большего целого числа. -
floor(x)– округляетxдо ближайшего меньшего целого числа. -
trunc(x)– отбрасывает дробную часть числаx.
round(15.64)
ceil(15.64)
floor(15.64)
trunc(15.64)
Функции деления
div(x,y)– целочисленное деление с усечением. Частное округляется "к нулю".fld(x,y)– целочисленное деление с округлением в меньшую сторону. Частное округляется в сторону минус бесконечности.cld(x,y)– целочисленное деление с округлением в большую сторону. Частное округляется в сторону плюс бесконечности.rem(x,y)– остаток от целочисленного деленияxнаy; удовлетворяет условиюx == div(x,y)*y + rem(x,y). Знак соответствуетx.mod(x,y)– остаток от целочисленного деленияxнаy; удовлетворяет условиюx == fld(x,y)*y + mod(x,y). Знак соответствуетy.mod2pi(x)– остаток от целочисленного деленияxна .
Примеры:
div(20,3)
fld(-20,3)
cld(20,3)
rem(20,3)
mod(-20,3)
Другие полезные функции
gcd(x,y)– наибольший положительный общий делитель чиселx,y.lcm(x,y)– наименьшее общее кратное чиселx,y.min(x,y)– минимальное значение из списка (для произвольного количества чисел в списке).max(x,y)– максимальное значение из списка (для произвольного количества чисел в списке).minmax(x,y)– минимальное и максимальное значения из двух чиселx,y; результат в форме кортежа.muladd(x,y,z)– вычисляет значениеx*y+z.hypot(x,y)– гипотенуза прямоугольного треугольника с катетамиxиy.eval(x)– вычислить значение выраженияx.real(x)– вещественная часть числаx.imag(x)– мнимая часть числаx.reim(x)– возвращает вещественную и мнимую частиx(в виде кортежа).conj(x)– число, комплексно-сопряженное к числуx.sign(x)– знак числаx, возвращает -1, 0 или +1.modf(x)– кортеж, содержащий дробную и целую части числаx. Обе части имеют тот же знак, что у аргумента.digits(x)– массив десятичных цифр, образующих целое число.isapprox()– позволяет сравнивать два числа с заданным уровнем погрешностиatol:
isapprox(1.0, 1.05; atol = 0.1) (true)
isapprox(1.0, 1.1; atol = 0.05) (false)
Примеры:
gcd(24,32)
lcm(24,32)
min(24, 33, 51, 22)
hypot(6,8)
reim(2+3im)
conj(2+3im)
sign(-5)
modf(432.657)
isapprox(12.47, 12.58, atol=0.01)
Специальные функции
Пакет SpecialFunctions.jl содержит множество дополнительных специальных математических функций. Среди них гамма-функция, бета-функция, интегральный синус, интегральный косинус, функция ошибок, функции Эйри, функции Бесселя, эллиптические интегралы, дзета-функция Римана и др.
Область видимости переменных
Под блоком кода будем понимать фрагмент кода, заключенный в синтаксическую конструкцию function (функция) или let … end. Внутри таких блоков кода можно объявлять локальные переменные, которые будут видны только внутри этого блока и не будут конфликтовать с переменными в других частях кода (например с глобальными переменными).
Область видимости определяет, где в коде вы можете использовать переменную. Так, переменные бывают локальные и глобальные.
Локальная (внутренняя) переменная – переменная, объявленная и определенная внутри своего родительского элемента, например функции, объекта, блока кода и т.д. Локальные переменные видны только внутри той области видимости, в которой они определены, и недоступны за ее пределами. Рассмотрим локальную переменную на примере:
function func()
local_var = 10
println(local_var)
end
Здесь function func() определяет функцию с именем func, содержащую локальную переменную local_var в локальной области видимости.
Вызов функции командой func() выведет значение переменной local_var, равное 10:
func()
После выполнения функции локальная переменная loval_var выходит из области видимости и становится недоступной за пределами функции. Это означает, что переменная существует только в пределах блока, в котором она была определена.
Попробуем вызвать переменную local_var вне локальной области с помощью следующего кода:
println(local_var)
UndefVarError: `local_var` not defined
Мы видим, что внешний код не имеет доступа к локальной переменной и выдает сообщение об ошибке о том, что локальная переменная local_var не определена.
Глобальная (внешняя) переменная – переменная, определенная вне функций и блоков кода с локальной областью видимости. Глобальная переменная доступна из любой части кода и находится в глобальной области видимости. Рассмотрим глобальную переменную на примере:
global_var = 20
function func()
println(global_var)
end
Этот код задает глобальную переменную global_var и определяет функцию func(), которая выводит значение глобальной переменной. Вызов функции командой func() выведет значение глобальной переменной global_var, равное 20:
func()
Глобальную переменную global_var можно использовать за пределами функции:
println(global_var)
Затенение переменных – это ситуация, когда внутри определенной области видимости (например, внутри функции или блока кода) используется переменная с тем же именем, что и в глобальной области. В результате переменная внутри области видимости затеняет (перезаписывает) переменную с тем же именем из внешней области.
Будьте внимательны при задании переменных с одинаковыми именами – из-за затенения переменная внутри ограниченной области может иметь иное значение или тип, чем переменная с тем же именем в других частях программы.
Для примера рассмотрим код с созданием двух переменных без затенения с одинаковыми именами и разными значениями:
x = 10 # создание глобальной переменной
function func()
x = 20 # создание локальной переменной (внутри функции) с тем же именем, что и глобальная переменная
println(x) # будет использована локальная переменная внутри функции
end
func() # вызов функции
println(x) # глобальная переменная не была изменена внутри функции, поэтому выводится ее значение
Внутри функции создается локальная переменная с тем же именем x, что и глобальная. Это не изменяет глобальную переменную, а создает новую локальную переменную, видимую только внутри функции. В результате выводятся значения обеих переменных, равные 20 (глобальная) и 10 (локальная) соответственно. Таким образом, создание переменных в разных областях видимости помогает избежать проблем с перезаписью переменных с одинаковыми именами.
В явном виде область видимости переменной можно задать с помощью одного из двух ключевых слов: global (глобальная) или local (локальная).
Например, определим функцию func, в которой задается значение переменной x. Чтобы эта переменная было доступна за пределами функции, сделаем ее глобальной, написав ключевое слово global перед именем переменной.
function func()
global x = 30
end
Вызов функции func() выведет значение переменной x, равное 30.
func()
Переменная x определена как глобальная, поэтому ее можно вызвать за пределами функции:
println(x)
Оператор let создает блок со строгой областью. Это означает, что если локальной переменной y еще не существует и присваивание происходит внутри конструкции, образующей строгую область (т.е. внутри блока, ограниченного операторами let и end), то создается новая локальная переменная с именем y в области присваивания.
Рассмотрим пример:
let y = 1
let y = 2
end
y
end
Здесь во внешнем блоке let создается локальная переменная y со значением 1. Во вложенном в него (внутреннем) блоке let создается своя локальная переменная y со значением 2. Вызов переменной y из внешнего блока выводит значение, присвоенное ей в этом блоке, т.е. 1. Если попытаться вызвать переменную y за пределами внешнего блока let, то возникнет ошибка, т.к. переменная y является локальной.
Можно использовать оператор let без аргументов, чтобы просто ввести новый блок области, не создавая новые привязки сразу же.
Следующий код полностью эквивалентен предыдущему:
let
local y = 1
let
local y = 2
end
y
end
Условные операторы
Операторы if и else
Условные операторы позволяют вычислять или не вычислять части кода в зависимости от значения логического выражения. Синтаксис условной конструкции if (если) – else (иначе) устроен следующим образом.
if логическое_выражение
действия_1
else
действия_2
end
Если логическое_выражение принимает значение true (истина), то выполняется последовательность действий действия_1. Если логическое_выражение принимает значение false (ложь), то выполняется последовательность действий действия_2. Завершается условная конструкция обязательным ключевым слово end.
Например, сравним два числа x и y. Если они равны, то на экран выводится сообщение: "x равно y". В противном случае (если x не равен y) выводится сообщение: "x не равно y". Фрагмент кода, выполняющий эти действия, представлен ниже:
if x == y
println("x равно y")
else
println("x не равно y")
end
Если выражение условия x == y имеет значение true, то соответствующий блок вычисляется; в противном случае вычисляется блок else. Вот как это работает на практике. Создадим функцию compare(x,y), которая сравнивает два аргумента x и y:
function compare(x, y)
if x == y
println("x равно y")
else
println("x не равно y")
end
end
Вызовем эту функцию с аргументами (1,2) и (3,3):
compare(1,2)
compare(3,3)
Оператор elseif
Еще один оператор условной конструкции elseif может быть использован после if для описания дополнительных условий для выполнения следующих блоков кода.
if логическое_выражение_1
действия_1
elseif логическое_выражение_2
действия_2
else
действия_3
end
Модифицируем функцию compare из предыдущего примера. Сравним два числа x и y. Если они равны, то на экран выводится сообщение: "x равно y". В противном случае, если x > y, то выводится сообщение: "x больше y". И, наконец, если последнее условие не выполняется (т.е. если x < y), то выводится сообщение: "x меньше y".
function compare(x, y)
if x == y
println("x равно y")
elseif x > y
println("x больше y")
else
println("x меньше y")
end
end
Проверим работу этой функции:
compare(4,4)
compare(6,5)
compare(7,8)
Блоки elseif и else являются необязательными. В одной условной конструкции может быть сколько угодно блоков elseif. Выражения условия в конструкции if – elseif– else вычисляются до тех пор, пока для одного из них не будет получено значение true, после чего вычисляется соответствующий блок. Последующие выражения условия или блоки не вычисляются.
Условные конструкции являются "открытыми", то есть не образуют локальную область. Это означает, что новые переменные, определенные внутри условной конструкции, можно использовать и после нее, даже если они не были определены ранее. Поэтому приведенную выше функцию compare можно было бы определить так:
function compare(x,y)
if x == y
relation = "равно"
elseif x > y
relation = "больше"
else
relation = "меньше"
end
println("x ", relation, " y")
end
Переменная relation объявлена внутри условной конструкции, но используется вне ее. Однако при использовании такой возможности значение переменной должно быть определено для каждого возможного пути выполнения кода.
Тернальный оператор ?:
Так называемый тернарный оператор ?: тесно связан с синтаксисом if – elseif – else, но применяется тогда, когда в зависимости от условия требуется выбрать значение одного из выражений, а не выполнить блок кода. Называется он так потому, что это единственный оператор, принимающий три операнда:
a ? b : c
Выражение a перед ? – это выражение условия. Если условие a равно true, то вычисляется выражение b перед :, а если оно равно false, то вычисляется выражение c после :. Обратите внимание, что пробелы вокруг ? и : обязательны: выражение a?b:c не является допустимым тернарным выражением.
Пример.
x = 1
y = 2
println(x == y ? "x равно y" : "x не равно y")
Если выражение x == y равно true, то результатом всего тернарного выражения является строка "x равно y"; в противном случае результатом будет строка "x не равно y".
Чтобы воспроизвести ситуацию из исходного примера с выбором из трех вариантов, потребуется построить цепочку из тернарных операторов:
compare(x, y) = println(x == y ? "x равно y" :
x > y ? "x больше y" : "x меньше y")
compare(1,1)
compare(2,3)
compare(5,4)
compare(9,10)
Практические примеры использования условных операторов
В качестве примера рассмотрим функцию для вычисления факториала, использующую условный оператор. На вход функции подается целое число n. Далее проверяются два условия. Если , то факториал отрицательного числа не может быть вычислен и на экран выводится сообщение об ошибке. Если , то . В противном случае факториал вычисялется по формуле .
function fact(n::Int)
if n < 0
return "n должно быть неотрицательным"
elseif n == 0
return n = 1
else
return n*fact(n-1)
end
end
Проверим работу этой функции, задавая ее аргумент равным -1, 0 и 5:
fact(-1)
fact(0)
fact(5)
Циклы
Для организации повторяющихся вычислений в Julia есть две конструкции: цикл for и цикл while.
Цикл for
Цикл for имеет следующую структуру:
for i = m:n
действия
end
Здесь i – переменная-счетчик, которая принимает значения из диапазона, заданного справа от знака равенства; m:n – объект-диапазон, представляющий собой последовательность чисел от m до n с шагом 1. Цикл for перебирает эти значения, по очереди присваивая их переменной i.
Пример. С помощью цикла for выведем на экран все натуральные числа от 1 до 10.
for i = 1:10
println(i)
end
Внутри тела цикла for всегда вводится новая переменная-счетчик независимо от того, есть ли переменная с таким именем во внешней области. Из этого следует, что, с одной стороны, переменную i не нужно объявлять перед циклом. С другой стороны, она будет недоступна вне цикла и не будет влиять на внешнюю переменную с тем же именем.
Внутри цикла по одной переменной может находиться второй цикл по другой переменной. Такой цикл называется двойным. С помощью двойного цикла удобно производить действия с элементами двумерных массивов (матриц). Количество вложенных друг в друга циклов может быть любым.
В общем случае в цикле for возможна итерация по любому контейнеру. В таких случаях в качестве равносильной альтернативы символу = применяется ключевое слово in или ∈, что делает код более понятным:
for i in [1, 4, 7, 2, 0]
println(i)
end
for s ∈ ["red", "green", "blue"]
println(s)
end
Несколько вложенных циклов for можно объединить в один внешний цикл, получив декартово произведение итерируемых объектов:
for i = 1:3, j = 4:6
println((i, j))
end
При использовании такого синтаксиса в итерируемых объектах по-прежнему можно ссылаться на переменные внешних циклов. Например, выражение for i = 1:n, j = 1:i будет допустимым.
В одном цикле for можно выполнять итерацию одновременно по нескольким контейнерам с помощью функции zip:
for (j, k) in zip([1 2 3], [4 5 6 7])
println((j,k))
end
С помощью функции zip создается итератор, представляющий собой кортеж из элементов переданных контейнеров. Итератор zip по порядку перебирает вложенные итераторы, выбирая -й элемент каждого из них на -й итерации цикла for. Когда элементы в каком-либо вложенном итераторе заканчиваются, выполнение цикла for останавливается.
Цикл while
Цикл while имеет следующую структуру:
while условие
действия
end
Здесь условие – некоторое логическое выражение. Пока оно принимает значение true, раз за разом выполняются действия в теле цикла while. Как только условие станет равным false, произойдет выход из цикла. Если на первой же итерации цикла условие равно false, то действия в теле цикла while не выполняются ни разу.
Пример. С помощью цикла while выведем на экран все натуральные числа от 1 до 10.
i = 1
while i <= 10
println(i)
i = i + 1
end
В теле цикла while переменная i должна изменять свое значение таким образом, чтобы на какой-нибудь итерации условие выполнения цикла i <= 10 приняло значение false. В противном случае цикл будет выполняться бесконечно долго.
Ключевое слово break
Иногда бывает необходимо завершить выполнение цикла while до того, как условие примет значение false, или прервать выполнение цикла for до того, как будет достигнут конец итерируемого объекта. Для этого можно использовать ключевое слово break, которое передает управление оператору, расположенному сразу после тела цикла.
Пример. Следующий код выводит на экран сумму всех натуральных чисел от 1 до 10.
i = 0
s = 0
while true
i = i + 1
s = s + i
if i >= 10
break
end
end
println(s)
Как только счетчик i достигнет значения 10, выполнение цикла будет прервано благодаря ключевому слову break.
Без ключевого слова break выполнение приведенного выше цикла while никогда бы не завершилось само по себе, т.к. условие, стоящее в заголовке цикла, всегда равно true.
Ключевое слово continue
В других случаях бывает полезно прервать итерацию и сразу перейти к следующей. Для этого служит ключевое слово continue.
Пример. Следующий код выводит на экран все числа, кратные 7 и принадлежащие отрезку .
for i = 1:100
if mod(i,7) != 0
continue
else
println(i)
end
end
В теле цикла проверяется условие: если переменная i не кратна 7 (т.е. если остаток от деления i на 7 не равен нулю), то мы переходим к следующей итерации цикла. В противном случае выводится на экран значение i.
Эту задачу можно было бы решить без использования continue, поменяв условие на противоположное и поместив вызов println внутрь блока if:
for i = 1:100
if mod(i,7) == 0
println(i)
end
end
На практике после ключевого слова continue следуют более сложные вычисления, а точек вызова continue обычно несколько.
Библиотеки
Установка и подключение библиотек
Библиотеки Julia – это организованный набор готовых функций, классов и объектов для решения различных задач. Библиотеки Julia распространяются в виде пакетов, которые можно установить с помощью менеджера пакетов Pkg и использовать в своих проектах.
В Engee работа с библиотеками Julia также осуществляется через встроенный менеджер пакетов Pkg с помощью двух инструментов рабочего пространства – командную строку и редактор скриптов.
При работе с большим количеством библиотек удобно использовать редактор скриптов, в котором после вызова Pkg не требуется его повторная инициализация. Командная строка удобна в том случае, если вам привычнее работать с кодом вне интерфейса редактора скриптов и вы работаете с небольшим количеством библиотек.
Принцип работы с библиотеками
Найдите точное название интересующей вас библиотеки. Экосистема Julia содержит более 10 000 пакетов, зарегистрированных в общем реестре, что может сильно затруднить поиск нужного пакета. Для оптимизации поиска рекомендуем обратиться к следующим источникам:
-
Внешние библиотеки – раздел документации Engee с описанием большого числа библиотек.
-
JuliaHub – сервис для поиска по всей зарегистрированной документации пакетов с открытым исходным кодом с возможностью сортировки по тегам и ключевым словам.
-
Julia Packages – онлайн-ресурс, предназначенный для поиска, изучения и просмотра пакетов Julia с возможностью настройки фильтров по категориям, популярности и датам.
-
Julia.jl – главный репозиторий пакетов для языка программирования Julia. Этот репозиторий хранится на платформе GitHub и содержит множество полезных пакетов и инструментов, разработанных сообществом для работы с языком Julia.
Выберите инструмент для работы с библиотеками – командная строка или редактор скриптов в зависимости от ваших задач.
Инициализируйте работу с пакетами в Julia, добавив менеджер пакетов Pkg. Синтаксис добавления менеджера отличается в зависимости от выбранного инструмента.
Добавьте библиотеки с помощью команды add в ваше окружение, чтобы использовать их в своем проекте. Синтаксис добавления библиотек зависит от выбранного инструмента.
Загрузите библиотеку в пространство имен и получите доступ к ее конкретным элементам с помощью команд using и import. Вы также можете добавить и собственные библиотеки.
При необходимости узнайте статус библиотек через status, проверьте, какие библиотеки загружены в оперативную память Engee, и удалите ненужные с помощью команд remove или rm. Синтаксис зависит от выбранного инструмента.
Библиотеки Julia в редакторе скриптов
Чтобы установить новую библиотеку в интерактивном скрипте Engee, в секции с кодом импортируйте менеджер пакетов Pkg командой import Pkg, а затем выполните команду Pkg.add("Имя библиотеки"). Например, секция с кодом для добавления библиотеки Example будет выглядеть так:
import Pkg
Pkg.add("Example")
После запуска этой ячейки будет импортирован менеджер пакетов Pkg и будет установлена библиотека Example. Информация о ходе установки библиотеки отобразится автоматически:
Resolving package versions...
Installed Example ─ v0.5.3
Updating `/user/start/Project.toml`
[7876af07] + Example v0.5.3
Updating `/user/start/Manifest.toml`
[7876af07] + Example v0.5.3
Как и в случае с командной строкой, вы можете добавить несколько библиотек одновременно, перечисляя их имена через запятую и добавляя квадратные скобки для создания массива строк, например:
Pkg.add(["Example", "Plots"])
Для удаления библиотеки используется команда rm:
Pkg.rm("Example")
Для удаления нескольких библиотек одновременно используется перечисление их имен через запятую и квадратные скобки:
Pkg.rm(["Example", "Plots"])
Команда remove не определяется в редакторе скриптов, хотя и используется в командной строке. Попытка вызвать команду remove в редакторе скриптов выдаст ошибку.
В редакторе скриптов имя менеджера пакетов Pkg и имена библиотек Julia чувствительны к регистру и должны начинаться с заглавной буквы, в противном случае вы получите сообщение об ошибке.
Статус библиотек
Узнать какие библиотеки установлены и готовы к использованию можно и через редактор скриптов:
-
Введите
import Pkg, если не сделали этого ранее. -
Введите
Pkg.status()для получения статуса библиотек:
Pkg.status()
Загрузка библиотек
В Julia для загрузки библиотек и предоставления доступа к их конкретным элементам, таким как функции, типы и переменные, используются операторы import и using.
Оба оператора позволят вам использовать функции из библиотек или загружать их, но есть небольшая разница в том, как они экспортируются в текущее пространство имен:
-
using– экспортирует все функции. -
import– требует явного указания, какие функции нужно импортировать.
Например:
import Base.Math.cos
x = cos(0.5)
В случае с import перед вызовом функции необходимо писать название библиотеки и ставить точку ..
Этот код импортирует функцию cos из библиотеки Base.Math и вызывает эту функцию с аргументом 0.5. Функция cos вычисляет косинус угла в радианах и присваивает это значение переменной x. Таким образом, переменная будет содержать результат вычисления косинуса угла 0.5 радиан, равный 0.8775825618903728.
Оператор using предназначен для загрузки библиотек целиком, а не отдельных функций. Тогда код с использованием оператора using будет иметь вид:
using Base.Math
y = cos(0.5)
Таким образом, using используется для загрузки библиотек и автоматического экспорта всех ее выбранных имен в текущее пространство имен, обеспечивая удобный доступ к функциям и переменным модуля. import также загружает библиотеку, но не экспортирует ее имена автоматически, что позволяет избежать возможных конфликтов имен и явно управлять импортированными элементами. Выбирайте операторы в зависимости от ваших задач.
Обзор основные библиотек
Ниже приведен краткий обзор некоторых часто используемых библиотек Julia.
Визуализация
Plots.jl– основная библиотека, предназначенная для визуализации данных (для построения различных видов графиков по числовым данным).PlotlyJS.jl– библиотека для визуализации данных. Предоставляет дополнительные возможности построения интерактивных графиков и сохранения графиков в файлах.
Системные библиотеки
Работа с файловыми форматами
MAT.jl– библиотека предоставляет инструменты для чтения и записи файлов данных формата MATLAB в Julia.JLD2.jl– библиотека позволяет сохранять и загружать структуры данных Julia в формате, представляющем собой подмножество стандарта HDF5.XLSX.jl– библиотека для чтения и записи файлов электронных таблиц Excel.CSV.jl– библиотека для работы с текстовыми данными с разделителями, будь то разделитель-запятая (CSV), разделитель-символ табуляции (TSV) или иной символ.FileIO.jl– библиотека предоставляет общий фреймворк для определения форматов файлов и диспетчеризации в соответствующие методы чтения и записи. Две основные функции в этом пакете называютсяloadиsave. Они обеспечивают высокоуровневую поддержку форматированных файлов, в отличие от низкоуровневых функцийreadиwriteв Julia.
Математические библиотеки
LinearAlgebra.jl– библиотека содержит множество функций для решения задач линейной алгебры.Roots.jl– библиотека содержит функции для решения уравнений вида .DifferentialEquations.jl– библиотека для численного решения обыкновенных дифференциальных уравнений.NumericalIntegration.jl– библиотека для численного интегрирования дискретизированных данных.ControlSystems.jl– библиотека содержит функции для анализа и синтеза систем управления (в первую очередь линейных).Interpolations.jl– библиотека содержит функции для различных методов интерполяции.DynamnicalSystems.jl– библиотека содержит функции для решения задач нелинейной динамики и анализа временных рядов.Calculus.jl– библиотека содержит функции для базовых операций дифференцирования и интегрирования.GLPK.jl– библиотека содержит функции линейного програмирования и линейной оптимизации.Ipopt.jl– библиотека для нелинейной оптимизации задач большой размерности.Distributions.jl– библиотека содержит функции для различных распределений вероятности.Polynomials.jl– библиотека обеспечивает основные арифметические действия, интегрирование, дифференцирование, вычисление значений, нахождение корней и аппроксимацию для одномерных многочленов.TaylorSeries.jl– библиотека содержит функции для разложения в ряд Тейлора функций одной или двух переменных.NLSolve.jl– библиотека содержит функции для решения систем нелинейных уравнений.Symbolics.jl– библиотека для символьных вычислений.Flux.jl– библиотека для машинного обучения.
Типы данных
DataFrames.jl– библиотека предоставляет инструментарий для работы с табличными данными.LaTeXStrings.jl– библиотека, упрощающая ввод уравнений LaTeX в переменных и константах строкового типа.
Обработка сигналов и связь
FFTW.jl– библиотека содержит функции для быстрого преобразования Фурье.Noise.jl– библиотека содержит функции для добавления различных видов шума к сигналам и изображениям.DSP.jl– библиотека содержит ряд функций для цифровой обработки сигналов (расчет периодограмм, оконные функции, синтез фильтров и фильтрация, свертка, корреляция, кодирование с линейным прогнозированием).DigitalComm.jl– библиотека предоставляет некоторые полезные инструменты для управления блоками цифровой связи (манипуляция с битами, модуляция и демодуляция, генерация и декодирование сигналов с несколькими несущими).
Обработка изображений
ImageBinarization.jl – библиотека содержит ряд алгоритмов для анализа и бинаризации изображений.
Статистика
StatsBase.jl– библиотека обеспечивает решение базовых задач статистики.HypothesisTests.jl– библиотека содержит различные функции для проверки статистических гипотез.GLM.jl– библиотека для построения линейных и обобщенных линейных моделей.
Библиотека Plots.jl
Библиотека Plots.jl представляет собой интерфейс и набор инструментов для визуализации данных.
Эта библиотека уже предустановлена в Engee, поэтому начать использовать ее можно с помощью следующей команды:
using Plots
Рассмотрим некоторые возможности этого пакета. Наиболее универсальный инструмент, служащий для построения графиков по числовым данным, – функция plot.
Пример. Построим график функци синус. Для координат x можно создать диапазон от 0 до 10, состоящий, скажем, из 100 элементов. Для координат y можно создать вектор, вычислив sin(x) поэлементно. Для этого мы пишем точку сразу после вызова функции. Наконец, мы используем функцию plot для построения линии.
x = range(0, 10, length=100)
y = sin.(x)
plot(x, y)
В Plots.jl каждый столбец представляет собой ряд – набор связанных точек, образующих линии, поверхности или другие примитивы построения графиков. Мы можем построить несколько линий, задав матрицу значений, где каждый столбец интерпретируется как отдельная линия. Ниже [y1 y2] формирует матрицу размером (100 строк, 2 столбца).
x = range(0, 10, length=100)
y1 = sin.(x)
y2 = cos.(x)
plot(x, [y1 y2])
Кроме того, можно добавлять на полотно дополнительные линии, изменяя объект plot. Для этого используется команда plot!, где ! обозначает, что команда изменяет текущий график.
y3 = sin.(x).^2 .- 1/2
plot!(x, y3)
Сохранение графиков выполняется с помощью команды savefig. Например:
savefig("myplot.png")
Графики в Julia можно стилистически оформить. В Plots.jl модификаторы графиков называются атрибутами. При работе с данными и атрибутами соблюдаются следующите правила: позиционные аргументы соответствуют входным данным, а именованные аргументы соответствуют атрибутам.
Так, например, plot(x, y, z) – это трехмерные данные для трехмерных графиков без атрибутов, а plot(x, y, attribute=value) – это двумерные данные с одним атрибутом, которому присвоено некоторое значение.
В качестве примера рассмотрим изменение ширины линии с помощью linewidth (или его псевдонима lw), изменение надписей условных обозначений с помощью label и добавление названия с помощью title. Применим все это к графикам синуса и косинуса:
x = range(0, 10, length=100)
y1 = sin.(x)
y2 = cos.(x)
plot(x, [y1 y2], title="Trigonometric functions", label=["sin(x)" "cos(x)"], linewidth=3)
Еще одним широко используемым видом графиков является график рассеяния (точки, не соединенные линиями). Он строится с помощью функции scatter. Например, построим график синуса и добавим к синусу случайную составляющую.
x = range(0, 10, length=100)
y = sin.(x)
y_noisy = y .+ 0.1*randn(100)
plot(x, y, label="sin(x)")
scatter!(x, y_noisy, label="data")
Больше о библиотеке Plots.jl вы можете узнать в документации.
Библиотека LinearAlgebra.jl
Библиотека LinearAlgebra.jl содержит реализацию множества часто используемых операций линейной алгебры.
Пример. Для матрицы A вычислим след, определитель и обратную матрицу с помощью функций tr, det и inv соответственно.
using LinearAlgebra
A = [1 2 3; 4 1 6; 7 8 1]
tr(A)
det(A)
inv(A)
Собственные значения и собственные векторы матрицы находятся с помощью функций eigvals и eigvecs соответственно:
A = [-4. -17.; 2. 2.]
eigvals(A)
eigvecs(A)
Кроме того, в библиотеке LinearAlgebra.jl доступно множество разложений матриц. Например, LU-разложение выполняется с помощью функции factorize:
A = [1.5 2 -4; 3 -1 -6; -10 2.3 4]
factorize(A)
Больше о библиотеке LinearAlgebra.jl вы можете узнать в документации.
Создание собственных библиотек
Для создания собственных библиотек используется команда module. Для этого нужно выполнить следующее:
- Создадим скрипт
moduleи назовем его так, как бы мы хотели назвать свою библиотеку (в нашем случаеTest_lib) и добавим содержимое модуля, например функциюget_value, которая просто возвращает входное значение:
module Test_lib
function get_value(x)
return x
end
end
- Для загрузки созданной библиотеки используем оператор
importилиusing.
import.Test_lib.get_value
или
using.Test_lib
- Далее получим требуемую функцию с помощью оператора
import:
import.Test_lib.get_value
import_value = get_value(7)
или с помощью оператора using:
using.Test_lib
using_value = get_value(7)
Добавление точки между import и именем библиотеки обязательно в случае добавления собственной библиотеки из module. Аналогично и для using.
Пример. Создадим библиотеку My_lib, содержащую одну функцию opp(x), которая возвращает число, противоположное входному значению. Затем загрузите функцию opp из библиотеки My_lib с помощью оператора import.My_lib.opp. После этого с помощью функции opp найдите число, противоположное числу 5.
# создаем библиотеку
module My_lib
function opp(x)
return -x
end
end
# используем библиотеку
import.My_lib.opp
opp(5)