JavaRush /Курси /Kotlin SELF /Кортежі: Pair і

Кортежі: Pair і Triple

Kotlin SELF
Рівень 10 , Лекція 1
Відкрита

1. Як повернути кілька значень із функції?

Якщо ви тільки починаєте програмувати, легко подумати: «Ну якщо мені треба повернути два значення — я просто… поверну два значення». На жаль, більшість мов (і Kotlin зокрема) зазвичай вимагають, щоб функція повертала рівно одне значення.

На такий випадок у програмістів є проста хитрість: повернути не «два значення», а один контейнер, усередині якого ці два значення зберігаються. І саме тут у Kotlin з’являються Pair і Triple.

Уявіть коробку з наліпкою «Усередині дві речі». Це і є Pair. А коробка з написом «Усередині три речі» — це Triple. Kotlin дає такі коробки «з коробки» (вибачте за каламбур), щоб ви не винаходили велосипед і не склеювали дані в рядок на кшталт "10,20".

Pair: контейнер із двох значень

Pair — це стандартний тип Kotlin, який зберігає рівно два пов’язані значення. Саме пов’язані: тобто такі, що логічно йдуть разом. Не «котлети й космічні ракети», а щось на кшталт «імʼя й вік», «координата X і координата Y», «код відповіді й текст повідомлення».

Важливо, що Pair не нав’язує компонентам сенсу. У нього немає полів x і y, немає name і age. Є лише чесні й трохи нудні first і second. Сенс з’являється з контексту та з імен змінних навколо.

Створюємо Pair через Pair(a, b) і читаємо через first/second

Найпряміший спосіб — викликати конструктор Pair(...).

fun main() {
    val point = Pair(10, 20)

    println(point.first)   // 10
    println(point.second)  // 20
}

Тут point — це «точка». Отже, ми домовилися, що first — це x, а second — це y. Kotlin цього не знає: він просто зберігає «перше» і «друге». А домовленість — уже на вашій совісті.

Приклад ближче до реальності: «значення + пояснення»

Іноді корисно зберігати число й короткий текст поруч — наприклад, «результат обчислення + коментар про те, що це взагалі було».

fun main() {
    val result = Pair(200, "OK")

    println("code=${result.first}")     // code=200
    println("message=${result.second}") // message=OK
}

Зверніть увагу: читання result.first мало що підказує про те, що саме там зберігається. Тому зазвичай або дають змінній конкретніше ім’я, або (у наступній лекції) розпаковують значення через деконструкцію. А сьогодні ми чесно тренуємося читати компоненти через first/second.

2. Triple: контейнер із трьох значень

Triple — це брат-близнюк Pair, тільки всередині в нього три значення. Це зручно, коли даних справді три й вони утворюють один смисловий набір. Класичний приклад — RGB: червоний, зелений, синій.

Як і у Pair, імена компонентів тут нудні, але очевидні: first, second, third. Kotlin ніби каже: «Я лише зберігаю. Сенс — ваша робота».

Створюємо Triple і читаємо через first/second/third

fun main() {
    val rgb = Triple(255, 128, 0)

    println("r=${rgb.first}")   // r=255
    println("g=${rgb.second}")  // g=128
    println("b=${rgb.third}")   // b=0
}

Шпаргалка: як читати компоненти

Коли очі втомлюються від first/second/third, корисно мати під рукою просту шпаргалку.

Тип Скільки значень зберігає Як читати компоненти
Pair
2
first, second
Triple
3
first, second, third

3. Зручне створення: to і виведення типів

Зараз буде маленький шматочок «магії Kotlin», але не лякайтеся: магія тут цілком добра.

Окрім Pair(a, b) є суперпопулярний запис:

val range = 1 to 10

Він створює Pair(1, 10).

Ця форма працює тому, що to — це функція, яку Kotlin дозволяє викликати в infix (інфіксній) формі: коли ім’я функції стоїть між двома значеннями. Kotlin описує правила infix-викликів (у функції має бути один параметр і ще кілька вимог) в окремій документації про infix-нотацію.

a to b — це пара, а не «спеціальний синтаксис»

Важливо розуміти: to — не «частина мови для пар», а звичайна функція (просто нею зручно користуватися в інфіксній формі). Тому a to b — це саме створення Pair(a, b).

Важливо: 1 to 10 — не діапазон 1..10

Дуже важливо: 1 to 10 — це не діапазон 1..10. Це просто пара значень, де first = 1, second = 10.

fun main() {
    val fromTo = 1 to 10

    println("from=${fromTo.first}") // from=1
    println("to=${fromTo.second}")  // to=10
}

Робіть імена змінних «промовистими»

Читабельність Pair майже завжди тримається на іменах змінних. Якщо назвати змінну вдало, код стає помітно привітнішим.

fun main() {
    val minMax = 3 to 12

    println("min=${minMax.first}") // min=3
    println("max=${minMax.second}") // max=12
}

Запис a to b настільки поширений, що Kotlin прямо використовує його в прикладах документації, коли потрібно «об’єднати пари».

Які типи зберігаються всередині Pair/Triple

Зараз буде важливий момент: Pair і Triple уміють зберігати що завгодно. Хочете Int і String — будь ласка. Хочете String і Boolean — теж можна. Kotlin при цьому часто сам виводить типи, як ми вже звикли у змінних.

Тобто, якщо ви пишете val p = 10 to 20, Kotlin розуміє, що це Pair<Int, Int>. Але сьогодні ми не заглиблюємося в деталі — до цього дійдемо значно пізніше. Поки що достатньо простого правила: типи всередині пари відповідають тому, що ви туди поклали.

Pair із різних типів

fun main() {
    val user = Pair("Ada", 36)

    println(user.first)   // Ada
    println(user.second)  // 36
}

Тут first — рядок, second — число. Kotlin сприймає це без проблем.

Triple — лише якщо це один сенс, а не «мішок усього»

Тільки пам’ятайте: Triple — не мішок «що б іще запхати». Якщо три значення не утворюють єдиної ідеї, згодом ви й самі дивитиметеся на це як на загадку.

fun main() {
    val movie = Triple("Matrix", 1999, true)

    println(movie.first)  // Matrix
    println(movie.second) // 1999
    println(movie.third)  // true
}

Працює? Так. Читається? Із натугою. Бо true — це «що»? «Дивився»? «Сподобалося»? «Фільм існує»? Ось тому Pair/Triple потрібно застосовувати обережно.

4. Застосунок: «чернетка витрат» у консолі

Щоб не залишати Pair і Triple у світі абстрактних точок і кольорів, почнемо розвивати маленький навчальний консольний застосунок. Нехай це буде супермінімальна «чернетка обліку витрат»: користувач вводить категорію та суму, а ми друкуємо зрозуміле підтвердження.

Сьогодні наше завдання просте: навчитися зберігати один запис витрати як зв’язку «категорія + сума». Для цього Pair підходить ідеально: два значення — один сенс.

Створюємо «витрату» як Pair

fun main() {
    val expense = "food" to 250

    println("Категорія: ${expense.first}") // Категорія: food
    println("Сума: ${expense.second}")  // Сума: 250
}

Так, food і 250 можна було зберігати у двох змінних. Але ідея така: іноді зручно передавати це далі як один об’єкт — «ось витрата цілком».

Читаємо дані й збираємо Pair

Ми вже вміємо користуватися readln() і toIntOrNull(). Не будемо ускладнювати: припустімо, користувач вводить категорію в першому рядку, а суму — у другому.

fun main() {
    print("Введіть категорію: ")
    val category = readln().trim()

    print("Введіть суму: ")
    val amountText = readln().trim()
    val amount = amountText.toInt()

    val expense = category to amount
    println("Збережено: ${expense.first} -> ${expense.second}") // приклад: Збережено: food -> 250
}

Це ще не «справжній» облік витрат, але як навчальний крок — чудово. Ми побачили, як Pair стає маленькою «упаковкою» для пов’язаної інформації.

Коли Pair/Triple доречні, а коли краще зупинитися

Pair і Triple — як скотч. У господарстві корисно, але якщо ви збудували космічний корабель із самого скотчу, то, можливо, десь проґавили етап проєктування.

Зазвичай Pair/Triple доречні, коли значення справді невеликі, їх мало, і ви швидко розумієте з контексту, що означають first і second. Наприклад, x to y для координати, min to max для меж, code to message для результату.

Але якщо ви ловите себе на тому, що постійно думаєте «а first — це що?» і починаєте писати коментарі на кшталт // first = category, second = amount, це сигнал: можливо, вам потрібен більш «іменований» результат. Але як правильно це зробити, я розповім вам десь через 10 рівнів.

5. Типові помилки під час роботи з Pair і Triple

Помилка № 1: плутанина в порядку компонентів (first/second).
Найпоширеніша проблема — переплутати, що лежить у first, а що в second. Особливо тоді, коли в різних місцях коду ви створюєте схожі пари, але «домовляєтеся по-різному»: десь (min, max), а десь (max, min). Це не помилка компілятора — це помилка сенсу. Рятує дисципліна: зафіксуйте порядок і тримайте його всюди однаковим, а змінним давайте імена, які цей порядок підказують (minMax, fromTo, xToY).

Помилка № 2: використовувати Pair для непов’язаних даних «бо можна».
Технічно ви можете покласти в Pair що завгодно. Але якщо два значення не утворюють одного сенсу, читання перетворюється на ребус. За тиждень ви відкриєте код і будете вгадувати, чому val x = pair.first, а x — це взагалі що? Pair — не «пакет із собою» для випадкових залишків.

Помилка № 3: сподіватися, що first означає «головне».
Іноді новачкам здається, що first — це «головне поле», а second — «додаткове». Kotlin так не вважає. Там немає головного й другорядного — є лише порядок. Якщо сенс вимагає «головності», це потрібно виражати іменами змінних і логікою коду, а не сподіватися на магію слова first.

Помилка № 4: Triple як «мішок усього».
Triple легко перетворюється на мініхаос: «і імʼя туди, і вік, і прапорець, і… ой, а четвертого місця немає». Якщо ви вже відчуваєте, що Triple не вміщує реальний сенс, це не «поганий Triple», а хороший сигнал: модель даних просить нормальнішої форми. Поки просто запам’ятайте цей сигнал — ми до нього повернемося в майбутньому, коли в нас з’являться власні типи.

Помилка № 5: довгі ланцюжки .first.first....
Коли ви починаєте бачити конструкції на кшталт something.first.second, це означає, що десь ви склеїли Pair усередині Pair і вже самі собі зробили лабіринт. У такій ситуації краще зупинитися й розкласти проміжні значення в змінні з нормальними іменами. Навіть якщо це на два рядки довше — мозку буде дешевше, ніж розгадувати «матрьошку з first’ів».

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ