ПрограммированиеBackend разработчик

Опишите механизм числовых диапазонов в Kotlin: как работают Range и Progression, как создавать собственные диапазоны и в каких сценариях их удобно применять?

Проходите собеседования с ИИ помощником Hintsage

Ответ

История вопроса

С самого начала в Kotlin были введены диапазоны (Range) для упрощения операций с последовательными наборами значений — это унаследовано от языков с лаконичным синтаксисом при работе с числами (например, Python). Кроме того, механизм Range был расширен до Progression, позволяя задавать шаг итерации и поддерживать разные типы чисел, символов.

Проблема

Итерирование по целым числам, буквам, временным точкам и т. д. часто требует особого синтаксиса и надежной поддержки в стандартной библиотеке, иначе код становится громоздким, нечитабельным и подвержен ошибкам по границам.

Решение

В Kotlin есть стандартные типы IntRange, CharRange и LongRange, а также Progression для шаговой итерации. Кроме того, можно определить диапазоны для произвольных сравнимых типов.

// Простой диапазон for (i in 1..5) print(i) // 12345 // Диапазон с шагом for (i in 1..10 step 2) print(i) // 13579 // Обратный диапазон for (i in 5 downTo 1) print(i) // 54321 // Собственный диапазон (например, для Version) data class Version(val major: Int, val minor: Int): Comparable<Version> { override fun compareTo(other: Version): Int = compareValuesBy(this, other, Version::major, Version::minor) } operator fun ClosedRange<Version>.iterator(): Iterator<Version> = object : Iterator<Version> { var current = start override fun hasNext() = current <= endInclusive override fun next() = current.also { current = Version(current.major, current.minor + 1) } } val v1 = Version(1, 0) val v2 = Version(1, 3) for (v in v1..v2) println(v)

Ключевые особенности:

  • Стандартный синтаксис диапазонов: .., downTo, until, step
  • Работа с числовыми, символьными, пользовательскими типами
  • Применение в циклах, проверке принадлежности, разбиении, валидации

Вопросы с подвохом.

Чем отличается выражение 1..5 от 1 until 5?

1..5 включает оба конца диапазона: 1,2,3,4,5. 1 until 5 не включает последний элемент: 1,2,3,4.

Можно ли задать диапазон с шагом менее нуля с использованием step?

Нет. Для убывающих диапазонов используйте конструкцию downTo, потом step: 5 downTo 1 step 2 (получите 5,3,1).

Можно ли использовать диапазоны с типами, не реализующими Comparable?

Нет. Для создания диапазона необходимо, чтобы тип поддерживал сравнение. В противном случае компилятор не допустит определение.

Типовые ошибки и анти-паттерны

  • Использование until вместо .. или наоборот, путаница с включенностью границ
  • Задание отрицательного шага для возрастающего диапазона (step не делает диапазон обратным)
  • Несоблюдение требований Comparable для пользовательских типов в диапазоне

Пример из жизни

Негативный кейс

В коде вместо 1 until n+1 использован 1..n. Получаем лишний элемент, цикл выходит за пределы допустимого диапазона.

Плюсы:

  • Непреднамеренно захватывается крайний случай

Минусы:

  • Ошибки при разбиении, неверные вычисления сумм на границе

Позитивный кейс

Используется for (i in 0 until n) для индексации массива длиной n, диапазон строго совпадает с допустимыми значениями индексов.

Плюсы:

  • Исключены ошибки выхода за пределы массива
  • Повышена читаемость

Минусы:

  • Требуется помнить разницу between .. и until при переходе между языками