JavaRush /Курси /Swift SELF /Повернення кількох значень

Повернення кількох значень

Swift SELF
Рівень 9 , Лекція 4
Відкрита

1. Повернення кількох значень і Tuple

Іноді задача виявляється занадто тісною для одного результату. Ви читаєте два числа й хочете відразу отримати і мінімум, і максимум. Або ділите число й хочете отримати і частку, і остачу. Можна, звісно, зробити два окремі виклики функцій, але тоді ви подвоюєте роботу, а ризик помилки теж зростає.

У реальній розробці це трапляється постійно: ми обчислюємо основне значення, а поруч нам потрібні корисні додаткові дані — наприклад, статус, межі, кількість або ознака успішності. Саме тут хочеться акуратного способу повернути не одне значення, а невеликий набір.

Tuple як «коробочка» для кількох результатів

Tuple (кортеж) у Swift — це спосіб зібрати кілька значень в одне ціле, не створюючи новий тип. Уявіть, що вам потрібно повернути «пару» або «трійку» результатів — логічно пов’язаних, але не настільки складних, щоб заради них заводити окрему сутність. Тоді tuple — це невеликий контейнер: він зберігає значення поруч, у фіксованому порядку, і дає змогу повернути їх одним return.

Важлива думка: tuple — це саме одне значення, яке повертається, просто всередині нього є кілька частин.

Найпростіший tuple‑результат без імен

Коли ви тільки починаєте, найпростіше побачити tuple на короткому прикладі. Ми хочемо повернути суму й добуток двох чисел — одразу обидва результати.

import Foundation

func sumAndProduct(_ a: Int, _ b: Int) -> (Int, Int) {
    let sum = a + b
    let product = a * b
    return (sum, product)
}

Тут -> (Int, Int) означає: «функція повертає tuple із двох Int». Повертаємо ми його буквально так само: return (sum, product).

Tuple з іменованими елементами

На практиці голі (Int, Int) швидко перетворюються на загадку: «а що там перше? а що друге?». Тому Swift дає змогу надати елементам tuple імена — і це помітно підвищує читабельність.

import Foundation

func sumAndProduct(_ a: Int, _ b: Int) -> (sum: Int, product: Int) {
    let sum = a + b
    let product = a * b
    return (sum: sum, product: product)
}

Тепер результат — це tuple з полями sum і product. Це значно зрозуміліше, ніж .0 і .1 — ми ще дійдемо і до них, не хвилюйтеся.

2. Як працювати з tuple‑результатом

Повернути tuple — це лише половина справи. Друга половина — зручно працювати з тим, що повернулося. Swift дає вам кілька способів: звертатися за індексами .0/.1, звертатися за іменами або розпакувати tuple одразу в кілька змінних.

Кожен спосіб має свій характер: індекси короткі, імена читабельні, а розпакування зручне, коли значення далі живуть окремо. Нижче розберемо всі три, щоб ви могли обирати не за звичкою, а за змістом.

Доступ за індексами .0, .1

Якщо елементи tuple не мають імен, або навіть якщо мають, ви все одно можете звернутися до них за порядковим номером: .0, .1, .2

import Foundation

let result = sumAndProduct(3, 5)
print(result.0) // 8
print(result.1) // 15

Такий код компілюється і працює, але має мінус: за тиждень або вже завтра зранку ви дивитеся на .0 і щиро не пам’ятаєте, що там було задумано. Це як змінна x1 — тільки гірше: у x1 принаймні є шанс бути першим іксом.

Доступ за іменами

Якщо tuple іменований, код починає читати сам себе: не потрібно тримати в голові порядок.

import Foundation

let r = sumAndProduct(3, 5)
print(r.sum)      // 8
print(r.product)  // 15

І тут є приємний нюанс: імена елементів — це про читабельність на рівні коду. З погляду компілятора tuple все одно залишається tuple, просто ви звертаєтеся до його частин зрозуміліше.

Розпакування

Іноді вам не потрібен «єдиний об’єкт результату», а потрібні одразу дві змінні. У таких випадках tuple можна розпакувати.

import Foundation

let (sum, product) = sumAndProduct(3, 5)	// Тут оголошуються дві змінні
print(sum)      // 8
print(product)  // 15

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

import Foundation

let (sum, _) = sumAndProduct(3, 5)
print(sum) // 8

3. Tuple-результат чи окремі параметри

Коли ви проєктуєте функцію, у вас зазвичай є вибір: повернути один компактний об’єкт — tuple — або передати значення окремо, через параметри, до наступної функції.

Важливо не плутати: окремі параметри зазвичай належать до входу функції, а tuple — до виходу. Але в дизайні коду це все одно вибір: тримати разом чи розносити по частинах.

Сьогодні ми не говоримо про «вихідні параметри», коли функція змінює зовнішню змінну. Ми порівнюємо два нормальні стилі: повертаємо tuple і передаємо його далі або одразу перетворюємо результати на окремі аргументи наступного виклику.

Повернути tuple і передавати далі як одне ціле

Цей стиль доречний, коли значення логічно утворюють набір: вони з’явилися разом, використовуються разом, і вам не хочеться щоразу пам’ятати порядок аргументів.

import Foundation

func minMax(_ a: Int, _ b: Int) -> (min: Int, max: Int) {
    if a <= b { return (min: a, max: b) }
    return (min: b, max: a)
}

func printMinMax(_ r: (min: Int, max: Int)) {
    print("min=\(r.min), max=\(r.max)")
}

І виклик:

import Foundation

let mm = minMax(10, 3)
printMinMax(mm) // min=3, max=10

Плюс цього підходу в тому, що ви не розкидаєте значення по коду. Мінус — іноді tuple живе занадто довго і перетворюється на «пакунок усього на світі», але це вже питання дисципліни.

Рознести значення і передавати як окремі параметри

Цей стиль доречний, коли наступна функція справді сприймає значення окремо, і ви хочете, щоб виклик був максимально явним. Особливо це корисно, коли параметри мають різний зміст і ви хочете підказати його через labels.

import Foundation

func printMinMax(min: Int, max: Int) {
    print("min=\(min), max=\(max)")
}

let mm = minMax(10, 3)
printMinMax(min: mm.min, max: mm.max) // min=3, max=10

Тут букв трохи більше, зате у виклику немає жодної загадки: min: і max: стоять на своїх місцях.

Мінітаблиця вибору

У реальності ви змішуватимете обидва підходи. Щоб кожен такий вибір не перетворювався на філософський трактат, зручно тримати просту таблицю-підказку:

Ситуація Частіше зручний tuple Частіше зручні окремі параметри
Значення логічно «йдуть комплектом» (min/max, quotient/remainder) Так Іноді
Значення потрібні як один об’єкт, який передається далі Так Рідше
Виклик має бути максимально самодокументованим на місці Іноді Так (labels допомагають)
Ви хочете розпакувати значення й працювати зі змінними окремо Можна, але не обов’язково Так (за змістом)

4. Приклад: мінізастосунок «PairAnalyzer»

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

Сьогодні ми створимо невеликий інструмент «PairAnalyzer»: він читає два цілі числа і друкує звіт — суму, добуток, мінімум і максимум. Важливо, що обчислення ми винесемо у функцію, яка повертає одразу кілька значень за допомогою одного tuple, а виведення зробимо окремою функцією — щоб не змішувати «обчислити» і «надрукувати».

Контракт застосунку: читання двох чисел

Оскільки введення в консолі — це завжди трохи лотерея: ніколи не знаєш, що введуть люди, — ми використовуємо найпростіший підхід: читаємо рядок і перетворюємо його на Int, а якщо не вдалося — беремо 0.

Це не ідеальна перевірка, але для поточного рівня — аж до теми обробки винятків — цілком нормальний робочий компроміс.

import Foundation

func readInt(prompt: String) -> Int {
    print(prompt, terminator: " ")
    let line = readLine() ?? ""
    return Int(line) ?? 0
}

Зверніть увагу: функція коротка, зрозуміла й має чіткий контракт — повернути Int за будь-яких умов.

Функція, яка повертає одразу 4 результати

Тепер створимо функцію, яка приймає два числа й повертає одразу набір результатів. Ми спеціально використовуємо іменований tuple, щоб код читався без «розшифрування .0/.1».

import Foundation

func analyzePair(_ a: Int, _ b: Int) -> (sum: Int, product: Int, min: Int, max: Int) {
    let sum = a + b
    let product = a * b
    let mm = minMax(a, b)
    return (sum: sum, product: product, min: mm.min, max: mm.max)
}

Тут важлива стильова ідея: ми не намагаємося зробити функцію-монстра, яка робить усе сама. Ми використовуємо minMax як маленький будівельний блок — це як LEGO: один кубик не будує замок, але й без кубиків замок не звести.

Окрема функція для друку звіту

Друк — це побічний ефект. Обчислення — чиста логіка. Якщо змішати їх, потім складніше змінювати формат виведення, повторно використовувати обчислення і тестувати код. Тому друк тримаємо окремо.

import Foundation

func printReport(_ r: (sum: Int, product: Int, min: Int, max: Int)) {
    print("sum=\(r.sum), product=\(r.product)")
    print("min=\(r.min), max=\(r.max)")
}

І тепер код верхнього рівня — тобто те, що реально запускається:

import Foundation

let a = readInt(prompt: "Введіть a:")
let b = readInt(prompt: "Введіть b:")

let report = analyzePair(a, b)
printReport(report)

Якщо запустити програму та ввести 10 і 3, вивід буде приблизно таким:

// Введіть a: 10
// Введіть b: 3
// sum=13, product=30
// min=3, max=10

Невелика схема потоку даних

Іноді корисно побачити, як дані течуть між функціями. Це допомагає не плутати: де вхід, де вихід, що повертається.

flowchart TD
    A[виклик readInt для a] --> X[a]
    B[виклик readInt для b] --> Y[b]
    X --> C[analyzePair]
    Y --> C
    C --> D[(кортеж звіту)]
    D --> E[printReport]

5. Типові помилки

Помилка № 1: спроба повернути різні tuple з різних гілок if/else.
Іноді новачки в одній гілці повертають (min: a, max: b), а в іншій — просто (b, a) або взагалі змінюють порядок. Компілятор Swift досить строгий: тип результату має збігатися. Якщо ви оголосили -> (min: Int, max: Int), то й повертати потрібно саме таку форму з тими самими типами.

Помилка № 2: зловживання .0/.1 і плутанина з порядком.
Доступ за індексами здається швидким, але це типовий «кредит під 0 %»: швидко зараз, боляче потім. Якщо tuple несе зміст (min/max, quotient/remainder), краще витратити дві секунди й дати елементам імена, щоб код читався людською мовою.

Помилка № 3: повертають tuple із 6–7 значень, а потім самі ж у ньому губляться.
Tuple добрий як компактне пакування невеликого набору пов’язаних значень. Але якщо результатів занадто багато, код починає виглядати як «посилка з десяти коробок усередині однієї». На цьому етапі зазвичай час переглянути дизайн: можливо, вам справді не потрібні всі значення або частину обчислень варто винести в окремі функції.

Помилка № 4: здивування, що мітки tuple не впливають на порівняння.
Swift не враховує labels під час порівняння tuple на рівність: якщо типи збігаються, (x: 0, y: 0) і (0, 0) можна порівняти, і вони вважаються рівними за значеннями елементів. Це описано в обговоренні поведінки tuple у Swift Evolution.

Помилка № 5: змішування обчислень і друку в одній функції «про всяк випадок».
Коли функція одночасно обчислює й друкує, її складніше повторно використовувати. Наприклад, ви захочете замість друку повернути рядок або змінити формат звіту — а логіка обчислень уже «впаяна» в print(). Набагато спокійніше тримати обчислення у функції, яка повертає дані (tuple), а виведення — в окремій функції, яка ці дані форматує.

1
Задача
Swift SELF, 9 рівень, 4 лекція
Недоступна
Підсумок і прибуток
Підсумок і прибуток
1
Задача
Swift SELF, 9 рівень, 4 лекція
Недоступна
Поділ нагороди
Поділ нагороди
1
Задача
Swift SELF, 9 рівень, 4 лекція
Недоступна
Мінімум і максимум
Мінімум і максимум
1
Задача
Swift SELF, 9 рівень, 4 лекція
Недоступна
Таймер трансляції
Таймер трансляції
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ