День 1 Летней школы Julia 2025
Составные выражения
В Julia имеются различные конструкции для управления порядком выполнения:
Сегодня мы с вами рассмотрим.
Составные выражения: begin и ;.
Условные вычисления: if-elseif-else и ?: (тернарный оператор).
Вычисления по сокращенной схеме: логические операторы && (И) и ||(ИЛИ), а также цепочки сравнения.
Повторяющиеся вычисления: циклы: while и for.
Начнём с составных выражений.
Иногда бывает удобно использовать выражение, в котором несколько подвыражений вычисляются по порядку и которое возвращает результат последнего подвыражения. Для этого в Julia есть две конструкции: блоки begin и цепочки через ;. Значением обеих конструкций составных выражений является результат последнего подвыражения.
z = begin
x = 1
y = 2
x + y
end
Обратите внимание на то, что x видно в "глобальной области видимости"
x
Схожим образом работает (expr1; expr2; expr3)
z = (x=10; y=100; x+y)
Условные вычисления
Условные вычисления позволяют вычислять или не вычислять части кода в зависимости от значения логического выражения.
Вспомним некоторые логические оперторы, которые мы рассмотрели в главе про операторы.
a = false || false # OR a = false
b = false && true # AND b = false
c = true ⊻ true # XOR c = false
a == b == c != true
Пускай у нас будет генерироваться случайное булевое значение - true или false.
Тогда, если эта переменная имела значение true, то происходит переход в внутрь блока if.
Если же значение оказалось отрицательным, то мы переходим в блок else.
Запустив ячейку несколько раз - мы получим разный результат.
condition = rand(Bool)
if condition
println("condition is $(condition)")
else
println("condition is $(condition)")
end
В отличие от С и С++ мы не можем подставлять целочисленные или какие угодно другие типы, кроме Bool в условный оператор. И мы даже не можем сконвертировать любые отличные от 0 и 1 числа в Bool.
if Bool(1) # Bool(2)
print(123)
end
Часто возникает ситуация, когда нужно произвести ветвление только на 2 случая: на случай true и на случай false. Тогда можно воспользоваться функцией ifelse, где первым аргументом передаётся условие, вторым - значение в случае true, а 3 аргументом - возвращаемое значение в противном случае.
x = 1
y = 2
ifelse(x > y, "x is larger","y is larger")
Аналогом этой функции выступает тернарный опертор.
Перед знаком вопроса стоит условие, после него идёт выражение, которое нужно вернуть в случае true, потом ставится : и идёт выражение, которое нужно вернуть в случае false.
# Вместо
x > y ? println("x > y") : (println("x ≤ y"))
# Лучше использовать
println(x > y ? "x > y" : "x ≤ y")
Отдельно хочется отметить, чем отличается тернарный оператор ? : от функции ifelse. Поскольку функция ifelse является функцией, то перед тем как она выполнится, выражения для её аргументов будут вычислены. Поэтому здесь происходит вывод сразу двух строк и возвращается nothing. Если бы наши аргументы не только печатали, но и возвращали непустые значения, то ifelse вернула бы это значение.
ifelse(x > y, println("x > y"), (println("x ≤ y")))
Вычисление по сокращённой схеме
Рассмотрим ситуацию, когда для логического ИЛИ передаются два аргумента.
Первый - true, второй - false. Если мы вычислим первый аргумент и он окажется true, то уже неважно, какое значение будет у второго аргумента, результат всегда будет true. Поэтому второй аргумент не будет вычисляться.
То же корректно и для логического И. Если первый аргумент false, то второй аргумент не нужно вычислять, результат операции всегда будет false.
Такое поведение называется вычислением по сокращённой схеме.
true || false
false && true
Давайте проверим, как это работает.
Для этого создадим функции, которые всегда возвращают true. Для этого воспользуемся разновидностью составных выражений, которые мы рассматривали в начале. Но вызов второй функции не сулит нам ничего хорошего.
Если мы запустим нашу программу, то выполнится только первый истинный аргумент, а до второго, к счастью, мы не дошли, благодаря сокращённой схеме.
нужно_ли_жить() = (println("Конечно же жить!");true)
взорвать_компьютер() = (println("БАБАХ!");true)
if нужно_ли_жить() || взорвать_компьютер()
println("Ну и денёк")
end
Циклы
Последней, из конструкций управления, мы рассмотрим циклы.
Цикл while выполняет итерации, пока условие его выполнения является истинным. Пользователь должен сам определить, как должно меняться условие. Если мы никак не будем менять это условие, то в случае его истинности, мы получим бесконечный цикл.
i = 10
while i >= 0
print("$(i^2) " )
i -=1
end
Но предлагаю рассмотреть обычную работу цикла, в которой на каждом шаге происходит работа с данными и изменение условия цикла посредством итератора.
Но на самом деле слово итератор в программировании имеет особый смысл.
Так, используя цикл for, мы можем итерироваться по элементам диапазона от 1 до 10
for i in 1:10
print("$(i^2) " )
end
Подробнее про итерирование с использованием цикла for мы будем говорить, когда основательно познакомимся с массивами и другими контейнерами.
А пока что обратим внимание на то, что мы можем пройтись по каждому элементу массива и напечатать его, пока не дойдём до знака стоп. В этот момент мы перейдём на строку, где происходит прерывание цикла с использованием ключевого слова break.
for elem in [1, "два", '🐫', [1, 2, 3], '⛔', "последний"]
if elem == '⛔'
break
else
println(elem)
end
end
Закончим же мы тем, что рассмотрим, как же итерироваться по элементам строки, с которыми у нас возникли проблемы, когда мы обращались по индексу 2. Для этого воспользуемся функцией eachindex. И напечатаем на новой строке символ и соответствующий ему индекс в строке.
s = "Всем привет!"
for i in eachindex(s)
println(s[i], ' ', i)
end
Заметим, что эти индексы изменяются в зависимости от размера символа.
Русские буквы итерируются с шагом 2, а знак пробела занимает 1 байт, поэтому ему соответствует шаг 1.