Цикли та Рекурсії
Тепер же, перейдемо до досить цікавої, але, знову ж, трішки непростої теми — цикли. Щоб більше зрозуміти, що таке цикли, створім якесь завдання. Наприклад, візьмемо завдання, яке ми вирішували у минулій темі. Для того, щоб розв'язати рівняння з використанням введення, ми щоразу запускаємо нашу програму. А що, якщо зробити в нашій програмі нескінченне введення, щоб щоразу не перезапускати нашу програму?
Взагалі, без нашої теми циклів це цілком можна було вирішити наступним чином:
fun main() {
println("Введіть число:")
val input: Double = readln().toDouble()
println("Результат: " + input.toString())
return main() // в кінці функції просто викликаємо її ще раз
}
І ось, рішення знайдено!
Подібне називають рекурсією. Простими словами - це поняття оголошення (напису, опису) коду функції через саму себе. Це як матрьошка, яка в нашому випадку не має кінця.
Що ж, а як тепер завершити нашу програму? Можна, звичайно, це зробити, закривши примусово процес програми через інструменти системи або IDE, але давайте будемо людьми й зробимо якийсь механізм виходу.
Щоб сильно не морочитися, введемо умову, що для виходу з програми нам потрібно написати «:q».
fun main() {
println("Введіть число: (або скористуйтесь :q для виходу):")
val input: String = readln() // створюємо змінну з текстом, тому що нам потрібно перевіряти введення користувача
if(input != ":q") {
val input: Double = input.toDouble() // сила областей видимості!
println("Результат: " + f(input).toString())
main()
}
}
Це так само залишиться рекурсією, тільки вже не нескінченною (у нас з'явилася умова).
Що ж, розглянувши досить простий приклад рекурсії, до якого можна було прийти самому під час спроби розв'язати завдання з перезапуском рішення рівнянь.
Що тоді таке цикли?
Цикли - це засоби мови, які відтворюють рекурсію. Їх також відносять до операторів, називаючи циклічними операторами.
Тож тепер розглянемо, як це можна вирішити іншими засобами мови. Не завжди ж ви створюватимете окремо функції для 'повторення чогось', так?
While
Для полегшення вам життя вигадали досить корисну конструкцію - while
.
Записується так:
while(boolean) {
// тут дія, що повторюється
}
Подібна конструкція виконує свій вміст у {}
, але перед кожним виконанням дивиться в умову (aka boolean-вираз)
і якщо там true
, то зміст виконується, а якщо false
- ні.
Наш попередній код можна виразити через while
наступним чином:
fun main() {
var shouldRun: Boolean = true
while(shouldRun) {
println("Введіть число (або скористайтесь :q для виходу):")
val input: String = readln()
if(input == ":q") {
shouldRun = false // при наступному виконанні цикл побачить, що умова `false`
} else {
val input: Double = input.toDouble()
println("Результат: " + f(input).toString())
}
}
}
Ось і наш перший цикл! Але якийсь він складний, вам не здається?
Все це можна спростити скориставшись спеціальними додатковими операторами: break
та continue
.
Що роблять ці два оператори? Розберімось.
break
(можна перекласти як розірвати, обірвати) – примусово закінчує цикл. Тобто навіть якщо умова будеtrue
цикл все одно закінчиться.continue
(перекладається як продовжити) - закінчує виконання поточного повторення. На відміну відbreak
,continue
, грубо кажучи, виходить з коду (код після нього не виконується) і переходить відразу до наступного повторення (до перевірки умови та подальшого повторення у разі, якщо тамtrue
).
Давайте перепишемо наш код:
fun main() {
while(true) { // умова нам не потрібна
println("Введите число (або скористайтесь :q для виходу):")
val input: String = readln()
if(input == ":q") {
break // виходимо з цикло
} else {
val input: Double = input.toDouble()
println("Результат: " + f(input).toString())
continue // взагалі, він необов'язковий у нашому випадку, але для наочності додамо
println("Я не надрукуюсь!") // IDE нам підкаже, що до цієї ділянки коду ми ніколи не дійдемо через continue
}
}
}
Через непотрібність ми викинули змінну shouldRun
, тому що є куди зручніший спосіб з break
.
Do-while
Одним із підвидів циклу while є do-while
. Крім назви, він відрізняється тим, що в do while спочатку
виконується тіло циклу, а потім перевіряється умова продовження циклу. Через таку особливість do while називають
циклом з постумовою, а звичайний while називають циклом з передумовою.
Записується так:
do {
// щось
} while(bool)
У такому циклі також існує break
та continue
, які ніяк не відрізняються.
Однак, наше завдання можна вирішити через do-while і без них:
// створимо змінну з повідомленням, щоб потім її перевикористовувати
val numberInputMessage = "Введите число (или :q для выхода):"
// створимо окрему функцію для зручності
private fun requestInput(message: String): String {
println(message)
return readln()
}
fun main() {
var input = requestInput(numberInputMessage)
do {
println("Результат: " + f(input.toDouble()).toString())
input = requestInput(numberInputMessage) // записуємо наступне введення, щоб перевірити після повторення, що було введено
} while(input != ":q") // якщо введення не ":q" програма продовжуватиме працювати
}
Ми створили для зручності функцію та змінну, що поєднувала схожий код. Знову зробили змінну поза циклом і запис її в кінці циклу (для того, щоб перевіряти після повторення введення користувача).
Це альтернативне рішення, хоч і не найкраще.
For
І тепер перейдемо до не менш важливого виду циклів - for
.
Відмінність цього виду циклів у тому, що він будується не за умови, а на ітераторі.
Що таке ітератор? Ітератор - це вбудована утиліта в мову, яка проходить між якоюсь сумою елементів.
Тобто кожне повторення буде відповідати одному елементу у цій сумі.
У нашому випадку, ця сума елементів буде відповідати діапазону, а елемент — одиниці рахування цього діапазону.
Що таке діапазон? Простими словами - інтервал значень будь-якої величини. Прикладом діапазону може бути [0; 5] (описує інтервал чисел від 0 до 5, включно). Бувають різні види діапазонів, але поки що ми розглянемо найпростіший варіант із діапазоном цілих чисел.
Як створити такий цикл? Для початку розглянемо прогресію з цілими числами:
for(i in 0..5) {
println(i)
}
Тут ми бачимо оператор in
, який працює з ітератором (у нашому випадку, з тим, що його виражає - діапазоном).
Цей код надрукує наступне:
0
1
2
3
4
5
Досить очевидно працює, чи не так?
Вирішім наступне завдання:
Відтворіть функцію степеня для позитивних чисел.
Еквівалентно функції Int.pow(x: Double)
.
fun pow(number: Int, times: Int): Int {
var output = number // створюємо змінну, де зберігається помножене значення
for(i in 1..times) { // через діапазон вказуємо, скільки разів має цикл повторитись
output *= number // множимо те, що вже є, на параметр number
}
return output // повертаємо число в степені
}
Тут нам IDE підкаже, що ідентифікатор не використовується і його бажано замінити на _
.
Річ у тому, що в котліні за код-стилем прийнято, що ідентифікатори, що не використовуються, називають саме так.
Що ж до завдання, тут нескладний імперативний варіант рішення.
Вирішім ще одне завдання:
Напишіть програму, де користувач вводить будь-яке ціле позитивне число. А програма підсумовує всі числа від 1 до введеного користувачем числа. Тобто, якщо введуть число 4, ми маємо підсумовувати такі числа: 1+2+3+4.
У цьому нам дуже допоможуть діапазони!
fun sum(input: Int): Int {
var output: Int = 0 // створюємо тимчасову змінну, що буде зберігати значення, що змінюється в циклі
for(i in 1..input)
output += i // можно прибрати `{}`, бо тут одна послідовність дій
return output
}
Ми створили тимчасову змінну і знову використали діапазони зі змінною i
,
що містить елемент інтервалу цього діапазону на кожну ітерацію (повторення) циклу
(який і відповідає тому, що ми по суті й робимо).
І наостанок, вирішимо ще одне завдання:
Дані натуральні числа від 1 до 50. Знайти суму з них, які діляться на 5 чи 7.
Перед розв'язанням цієї задачі, згадаємо один з арифметичних операторів - %
(залишок від поділу).
fun main() {
println(22 % 4)
println(4 % 2)
}
Надрукує 2
і 0
, оскільки буде такий залишок після поділу (у першому не ділиться націло, у другому - ділиться).
Наше завдання полягає в тому, щоб знайти числа, що діляться націло на 5 та 7. Це буде еквівалентно наступному:
number % 5 == 0 || number % 7 == 0
Ця умова нам підходитиме. Тепер же залишається тільки зробити цикл та тимчасову змінну в яку ми додаватимемо результат.
fun main() {
var temp: Int = 0
for(i in 1..50)
if(i % 5 == 0 || i % 7 == 0)
temp += i
println("Сума: " + temp)
}
Відповіддю у нас має вийти: 436
.