Kotlin для початківців: Основи — Цикли та Рекурсія

Архів
22 листопада 2022 р.7 хв читання
Kotlin для початківців

Цикли та Рекурсії

Тепер же, перейдемо до досить цікавої, але, знову ж, трішки непростої теми — цикли. Щоб більше зрозуміти, що таке цикли, створім якесь завдання. Наприклад, візьмемо завдання, яке ми вирішували у минулій темі. Для того, щоб розв'язати рівняння з використанням введення, ми щоразу запускаємо нашу програму. А що, якщо зробити в нашій програмі нескінченне введення, щоб щоразу не перезапускати нашу програму?
Взагалі, без нашої теми циклів це цілком можна було вирішити наступним чином:
kotlin
fun main() { println("Введіть число:") val input: Double = readln().toDouble() println("Результат: " + input.toString()) return main() // в кінці функції просто викликаємо її ще раз }
І ось, рішення знайдено!
Подібне називають рекурсією. Простими словами - це поняття оголошення (напису, опису) коду функції через саму себе. Це як матрьошка, яка в нашому випадку не має кінця.
Що ж, а як тепер завершити нашу програму? Можна, звичайно, це зробити, закривши примусово процес програми через інструменти системи або IDE, але давайте будемо людьми й зробимо якийсь механізм виходу.
Щоб сильно не морочитися, введемо умову, що для виходу з програми нам потрібно написати «:q».
kotlin
fun main() { println("Введіть число: (або скористуйтесь :q для виходу):") val input: String = readln() // створюємо змінну з текстом, тому що нам потрібно перевіряти введення користувача if(input != ":q") { val input: Double = input.toDouble() // сила областей видимості! println("Результат: " + f(input).toString()) main() } }
Це так само залишиться рекурсією, тільки вже не нескінченною (у нас з'явилася умова).
Що ж, розглянувши досить простий приклад рекурсії, до якого можна було прийти самому під час спроби розв'язати завдання з перезапуском рішення рівнянь.
Що тоді таке цикли?
ℹ️ Визначення
Цикли - це засоби мови, які відтворюють рекурсію. Їх також відносять до операторів, називаючи циклічними операторами.
Тож тепер розглянемо, як це можна вирішити іншими засобами мови. Не завжди ж ви створюватимете окремо функції для 'повторення чогось', так?

While

Для полегшення вам життя вигадали досить корисну конструкцію - while. Записується так:
kotlin
while(boolean) { // тут дія, що повторюється }
Подібна конструкція виконує свій вміст у {}, але перед кожним виконанням дивиться в умову (aka boolean-вираз) і якщо там true, то зміст виконується, а якщо false - ні.
Наш попередній код можна виразити через while наступним чином:
kotlin
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).
Давайте перепишемо наш код:
kotlin
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 називають циклом з передумовою.
Записується так:
kotlin
do { // щось } while(bool)
У такому циклі також існує break та continue, які ніяк не відрізняються. Однак, наше завдання можна вирішити через do-while і без них:
kotlin
// створимо змінну з повідомленням, щоб потім її перевикористовувати 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, включно). Бувають різні види діапазонів, але поки що ми розглянемо найпростіший варіант із діапазоном цілих чисел.
Як створити такий цикл? Для початку розглянемо прогресію з цілими числами:
kotlin
for(i in 0..5) { println(i) }
Тут ми бачимо оператор in, який працює з ітератором (у нашому випадку, з тим, що його виражає - діапазоном).
Цей код надрукує наступне:
plaintext
0 1 2 3 4 5
Досить очевидно працює, чи не так?
Вирішім наступне завдання:
ℹ️ Завдання
Відтворіть функцію степеня для позитивних чисел. Еквівалентно функції Int.pow(x: Double).
kotlin
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.
У цьому нам дуже допоможуть діапазони!
kotlin
fun sum(input: Int): Int { var output: Int = 0 // створюємо тимчасову змінну, що буде зберігати значення, що змінюється в циклі for(i in 1..input) output += i // можна прибрати `{}`, бо тут одна послідовність дій return output }
Ми створили тимчасову змінну і знову використали діапазони зі змінною i, що містить елемент інтервалу цього діапазону на кожну ітерацію (повторення) циклу (який і відповідає тому, що ми по суті й робимо).
І наостанок, вирішимо ще одне завдання:
ℹ️ Завдання
Дані натуральні числа від 1 до 50. Знайти суму з них, які діляться на 5 чи 7.
💡 Згадайте
Перед розв'язанням цієї задачі, згадаємо один з арифметичних операторів - % (залишок від поділу).
kotlin
fun main() { println(22 % 4) println(4 % 2) }
Надрукує 2 і 0, оскільки буде такий залишок після поділу (у першому не ділиться націло, у другому - ділиться).
Наше завдання полягає в тому, щоб знайти числа, що діляться націло на 5 та 7. Це буде еквівалентно наступному:
kotlin
number % 5 == 0 || number % 7 == 0
Ця умова нам підходитиме. Тепер же залишається тільки зробити цикл та тимчасову змінну в яку ми додаватимемо результат.
kotlin
fun main() { var temp: Int = 0 for(i in 1..50) if(i % 5 == 0 || i % 7 == 0) temp += i println("Сума: " + temp) }
Відповіддю у нас має вийти: 436.