JavaRush /Курсы /Swift SELF /switch с диапазонами и where‑условиями

switch с диапазонами и where‑условиями

Swift SELF
8 уровень , 3 лекция
Открыта

1. Диапазоны в switch

Когда вы впервые пишете проверку «попадает ли число в интервал», почти всегда получается что-то вроде x >= 0 && x <= 100. Это нормально, но ровно до того момента, пока таких проверок не станет десять. Тогда код превращается в набор магических чисел, где легко ошибиться в границе и потом героически чинить баг «почему 100 не считается отличным баллом».

Диапазоны в switch решают эту проблему: вы начинаете писать правила так, как вы их проговариваете вслух. «Если от 0 до 59 — провал», «если от 60 до 89 — нормально», «если от 90 до 99 — отлично», «если 100 — идеально». Это не только короче, но и заметно читабельнее.

Для примера сегодня мы будем развивать мини‑приложение TicketBot — консольный помощник для расчёта категории билета (детский/взрослый/льготный) и простых скидок. Пока без функций и без коллекций: всё на top‑level коде, как вы уже делали раньше.

Диапазон как паттерн: case 0..<10

Когда switch сравнивает значение с case, он не обязан сравнивать только с конкретным числом. В Swift case — это «паттерн», то есть шаблон совпадения. Таким паттерном может быть диапазон. Вы пишете case 0..<10: — и это читается как «значение попадает в диапазон от 0 до 9».

Важно уловить мысль: здесь мы не пишем условие в стиле if. Мы задаём шаблон, которому значение либо соответствует, либо нет. Это очень похоже на сортировку писем по ящикам: вы не пересчитываете каждое письмо заново, вы просто кладёте его в правильный слот по правилу.

Простой пример с «оценкой по баллам»:

import Foundation

let score = 97

switch score {
case 90..<100:
    print("Отлично, но не максимум") // Отлично, но не максимум
case 0..<90:
    print("Есть куда расти")         // (не выполнится)
default:
    print("Некорректный балл")       // (не выполнится)
}

Здесь 90..<100 означает «90, 91, …, 99». Значение 100 туда не входит.

2. Границы и порядок case

... против ..<

Когда речь о диапазонах, самая частая ошибка новичка — перепутать «включительно» и «не включительно». Это не мелочь: одна неправильная точка может превратить 18-летнего пользователя в «несовершеннолетнего». Поэтому сейчас мы притормозим и закрепим границы спокойно, без спешки.

Сделаем небольшую таблицу, которую полезно держать в голове:

Запись диапазона Название Правая граница Пример чтения
a...b
закрытый диапазон включается «от a до b включительно»
a..<b
полуоткрытый диапазон не включается «от a до (b-1) включительно»

Теперь пример, где границы реально важны. Мы хотим классифицировать баллы так, чтобы 100 обрабатывался отдельно:

import Foundation

let score = 100

switch score {
case 100:
    print("Идеально!")               // Идеально!
case 90..<100:
    print("Почти идеально")
case 60..<90:
    print("Нормально")
case 0..<60:
    print("Провал")
default:
    print("Некорректное значение")
}

Обратите внимание на очень полезный приём: мы ставим case 100: выше, а затем используем 90..<100, чтобы диапазоны не пересекались. Такой код легко проверять глазами: 059, 6089, 9099, 100.

Односторонние диапазоны: ..<0

Иногда вы хотите обработать «все значения меньше некоторого порога» или «все значения больше/равно порога». Писать для этого отдельные диапазоны с огромными числами — сомнительное удовольствие (и да, -999999999...-1 звучит как заклинание из фэнтези).

Swift позволяет использовать односторонние диапазоны прямо в case. Например, case ..<0: означает «всё, что меньше нуля». Это удобно для валидации вводимых чисел: сначала ловим совсем неправильные значения, а потом работаем с нормальными.

Пример для TicketBot: пользователь вводит возраст, а мы сразу отсеиваем «минус пять лет».

import Foundation

let age = Int(readLine() ?? "") ?? -1

switch age {
case ..<0:
    print("Возраст не может быть отрицательным")  // если ввели -5
case 0...120:
    print("Возраст принят: \(age)")
default:
    print("Слишком фантастический возраст")
}

Такой switch хорош тем, что он читается как «проверка здравого смысла». И да, ..<0 выглядит как смайлик, который грустит, потому что возраст отрицательный.

Почему важен порядок case

На предыдущей лекции вы уже видели, что switch идёт сверху вниз и срабатывает первый подходящий case. Сейчас это становится ещё важнее, потому что диапазоны могут пересекаться. Если поставить «широкий» диапазон выше «узкого», узкий никогда не сработает — его уже «поймали» раньше.

Давайте специально сделаем пример с ошибкой и посмотрим, почему он логически плох:

import Foundation

let score = 95

switch score {
case 0..<100:
    print("Любой валидный балл")     // сработает это
case 90..<100:
    print("Отлично")                 // а сюда мы никогда не попадём
default:
    print("Некорректно")
}

Правильная мысль здесь простая: сначала ставим более конкретные правила, потом — более общие. Исправим порядок:

import Foundation

let score = 95

switch score {
case 90..<100:
    print("Отлично")                 // Отлично
case 0..<90:
    print("Обычный балл")
default:
    print("Некорректно")
}

Если держать это правило в голове, вы будете ловить половину багов ещё до запуска программы: просто глазами.

3. where в case

Дополнительное условие через where

Иногда одного диапазона мало. Например, вы хотите написать правило: «положительное число и при этом чётное». Можно сделать вложенный if внутри case, но тогда switch начинает распухать и теряет свою прелесть «плоской» структуры.

Для таких ситуаций в Swift у case есть уточнение where: дополнительное условие, которое должно быть истинным, чтобы ветка считалась подходящей. В результате вы пишете правило в одном месте, не плодите вложенности и не превращаете код в матрёшку.

Классический пример — различить чётные и нечётные среди положительных:

import Foundation

let x = 12

switch x {
case let v where v > 0 && v.isMultiple(of: 2):
    print("Положительное чётное: \(v)")   // Положительное чётное: 12
case let v where v > 0:
    print("Положительное нечётное: \(v)")
case 0:
    print("Ноль")
default:
    print("Отрицательное")
}

Здесь используется isMultiple(of:) как понятный способ проверить кратность.

Зачем почти всегда нужен case let v

На первый взгляд может показаться, что писать case let v странно. Ведь у нас уже есть переменная x. Почему бы не использовать её?

Ответ становится понятнее, если вспомнить, как работает switch. Когда вы пишете switch x, значение x передаётся внутрь механизма сопоставления с паттернами. Дальше switch сравнивает это значение с каждым case. Но внутри ветки case вы работаете уже не с исходной переменной, а с тем значением, которое совпало с паттерном.

Это особенно заметно, когда в switch передаётся не переменная, а выражение. Например, можно сделать даже так:

import Foundation

switch Int(readLine() ?? "") ?? 0 {
case let v where v > 0:
    print("Положительное число: \(v)")
default:
    print("Ноль или отрицательное")
}

Здесь в switch передано выражение Int(readLine() ?? "") ?? 0. У него нет имени. После того как switch получил результат этого выражения, обратиться к нему напрямую уже нельзя — переменной для этого просто не существует. Поэтому в case мы создаём имя для совпавшего значения. Запись case let v говорит компилятору: если значение подошло под этот case, привяжи его к имени v, чтобы я мог использовать его внутри ветки.

После такой привязки значение становится обычной переменной. Его можно проверять, передавать в функции или выводить на экран. Именно поэтому условие в примере выглядит так: case let v where v > 0. Сначала мы связываем совпавшее значение с именем v, а затем уточняем правило через where.

4. Практика: TicketBot

Теперь давайте соберём это в небольшой, но практичный пример TicketBot. Мы будем спрашивать возраст и час посещения (023), а затем определять категорию и скидку «ранняя птица» до 10 утра. Цель не в том, чтобы сделать идеальную кассу кинотеатра, а в том, чтобы увидеть: диапазоны — это классификация, where — это уточнение правила.

Сначала нормализуем ввод и заведём пару констант‑диапазонов, чтобы не повторять числа:

import Foundation

let age = Int(readLine() ?? "") ?? -1
let hour = Int(readLine() ?? "") ?? -1

let validAge = 0...120
let validHour = 0...23

Теперь основной switch по возрасту. Обратите внимание: в case можно использовать переменную‑диапазон (у нас validAge, validHour), потому что диапазон — это значение, а case умеет матчить по паттерну‑диапазону.

import Foundation

let age = Int(readLine() ?? "") ?? -1
let hour = Int(readLine() ?? "") ?? -1

let validAge = 0...120
let validHour = 0...23

if !validAge.contains(age) || !validHour.contains(hour) {
    print("Некорректный ввод")
} else {
    switch age {
    case 0..<7:
        print("Категория: малыш")
    case 7..<18:
        print("Категория: детский")
    case 18..<65:
        print("Категория: взрослый")
    default:
        print("Категория: льготный")
    }
}

Пока мы сделали только классификацию. Теперь добавим скидку по времени. Можно было бы городить второй switch отдельно по hour, но мы потренируемся использовать where и сделаем «взрослый билет утром дешевле».

Идея такая: в ветке «взрослый» мы добавим уточняющую ветку раньше, чем обычную взрослую. Это как раз тот случай, когда порядок case и where работают вместе.

import Foundation

let age = Int(readLine() ?? "") ?? -1
let hour = Int(readLine() ?? "") ?? -1

let validAge = 0...120
let validHour = 0...23

if !validAge.contains(age) || !validHour.contains(hour) {
    print("Некорректный ввод")
} else {
    switch age {
    case let a where (18..<65).contains(a) && hour < 10:
        print("Категория: взрослый (скидка за ранний сеанс)") // если hour = 9
    case 0..<7:
        print("Категория: малыш")
    case 7..<18:
        print("Категория: детский")
    case 18..<65:
        print("Категория: взрослый")
    default:
        print("Категория: льготный")
    }
}

Заметьте, что в where мы используем hour, который лежит снаружи. Это нормально: where — обычное условие, оно видит переменные из области видимости.

И ещё одно важное наблюдение: мы намеренно не тащим сюда будущие темы вроде функций или пользовательских типов. Да, TicketBot пока выглядит «простынёй», но на текущем этапе это честная простыня: всё, что в ней есть, вы уже умеете читать.

5. Типичные ошибки при switch с диапазонами и where

Ошибка №1: перепутать ... и ..< и потерять граничное значение.
Это классика жанра: вы хотели «до 18 включительно», написали 0..<18 и внезапно 18 оказалось «взрослым». Лечится просто: для каждого диапазона мысленно проверяйте правую границу, а ещё лучше — прогоняйте в голове 23 тестовых значения: левую границу, правую границу и значение «сразу после».

Ошибка №2: пересечь диапазоны и удивляться, почему срабатывает «не та ветка».
Если у вас есть case 0..<100: и ниже case 90..<100:, то «отлично» никогда не случится, потому что 95 уже попало в верхний case. Это не баг Swift, это честное следование порядку: switch идёт сверху вниз. Лекарство — сначала ставить более узкие правила, потом более широкие.

Ошибка №3: сделать where слишком тяжёлым и перестать понимать собственный код.
where соблазняет: «О, я сейчас напишу одно гигантское условие и всё решу». Потом вы возвращаетесь к коду через два дня и чувствуете себя археологом. Держите правило: одна ветка — одна мысль. Если условие стало слишком длинным, лучше разбить логику на несколько case или хотя бы упростить проверку через заранее подготовленные диапазоны.

Ошибка №4: забыть привязку значения в case let v where ... и пытаться использовать v.
where сам по себе не создаёт имя. Если вы написали что-то в духе case 0..<100 where v.isMultiple(of: 2), компилятор справедливо спросит: «а кто такой v?». Чтобы использовать значение, его нужно связать: case let v where ... Такая привязка — нормальная часть паттернов switch.

Ошибка №5: пытаться писать в case «условие как в if», например case x > 0:.
Это популярный рефлекс после if. В switch так нельзя: case — это паттерн (литерал, диапазон, _, и так далее), а для «условной логики» как раз существует where. Если хочется «больше нуля», пишем case let v where v > 0: и радуемся, что получилось читаемо.

1
Задача
Swift SELF, 8 уровень, 3 лекция
Недоступна
Итог теста
Итог теста
1
Задача
Swift SELF, 8 уровень, 3 лекция
Недоступна
Режим лампы
Режим лампы
1
Задача
Swift SELF, 8 уровень, 3 лекция
Недоступна
Дверной датчик
Дверной датчик
1
Задача
Swift SELF, 8 уровень, 3 лекция
Недоступна
Билет музея
Билет музея
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ