Диапазоны и прогрессии

Kotlin позволяет легко создавать диапазоны значений с помощью функции rangeTo(), которая находится в пакете kotlin.ranges. У функции есть операторная форма - ... Обычно rangeTo() используется совместно с функциями in или !in.

if (i in 1..4) {  // эквивалентная запись 1 <= i && i <= 4
    print(i)
}

У диапазонов целочисленных типов (IntRange, LongRange, CharRange) есть дополнительная функция: они поддерживают итерацию. Эти диапазоны также являются прогрессиями.

Подобные диапазоны, как правило, используются в цикле for.

fun main() {
    for (i in 1..4) print(i) // 1234
}

Для перебора чисел в обратном порядке используйте функцию downTo вместо ...

fun main() {
    for (i in 4 downTo 1) print(i) // 4321
}

Можно перебирать числа с произвольным шагом. Осуществляется это с помощью функции step.

fun main() {
    for (i in 1..8 step 2) print(i) // 1357
    println()
    for (i in 8 downTo 1 step 2) print(i) // 8642
}

Если требуется перебрать диапазон чисел, исключая его последний элемент, то используйте функцию until.

fun main() {
    for (i in 1 until 10) { // i in [1, 10), 10 будет исключён
        print(i) // 123456789
    }
}

Диапазоны

В математическом смысле диапазон - это закрытый интервал: он определяется двумя значениями и они оба являются частью диапазона. Диапазоны применимы к сопоставимым (comparable) типам: имея порядок, вы можете определить, находится ли произвольный экземпляр в диапазоне между двумя заданными экземплярами.

Основная операция с диапазонами - это contains, которая обычно используется в форме операторов in и !in.

Чтобы создать диапазон на основе ваших классов, вызовите функцию rangeTo() для начального значения диапазона и укажите конечное значение в качестве аргумента. Чаще всего используется операторная форма функции rangeTo() - ...

class Version(val major: Int, val minor: Int): Comparable<Version> {
    override fun compareTo(other: Version): Int {
        if (this.major != other.major) {
            return this.major - other.major
        }
        return this.minor - other.minor
    }
}

fun main() {
    val versionRange = Version(1, 11)..Version(1, 30)
    println(Version(0, 9) in versionRange) // false
    println(Version(1, 20) in versionRange) // true
}

Прогрессии

Как показано в приведённых выше примерах, диапазоны целочисленных типов, таких как Int, Long и Char, можно рассматривать как арифметические прогрессии. В Kotlin есть специальные типы для определения таких прогрессий: IntProgression, LongProgression и CharProgression.

У прогрессий есть три основных свойства: first, last и step, при этом step не может быть нулём. first - это первый элемент. Последующие элементы - это предыдущий элемент плюс step. Итерация по прогрессии с положительным шагом (step) эквивалентна индексируемому циклу for в Java / JavaScript.

for (int i = first; i <= last; i += step) {
  // ...
}

При неявном создании прогрессии путём итерации диапазона, элементы first и last этой прогрессии являются конечными точками диапазона, а step равен 1.

fun main() {
    for (i in 1..10) print(i) // 12345678910
}

Чтобы прогрессии задать собственный шаг, используйте функцию step.

fun main() {
    for (i in 1..8 step 2) print(i) // 1357
}

Последний элемент прогрессии (last) рассчитывается следующим образом:

  • Для положительного шага: максимальное значение, но не больше конечного значения - (last - first) % step == 0.
  • Для отрицательного шага: минимальное значение, но не меньше конечного значения - (last - first) % step == 0.

Таким образом, элемент last не всегда совпадает с конечным значением диапазона.

fun main() {
    for (i in 1..9 step 3) print(i) // 147, last = 7
}

Чтобы создать прогрессию для итерации в обратном направлении, при определении диапазона используйте downTo вместо ...

fun main() {
    for (i in 4 downTo 1) print(i) // 4321
}

Прогрессии реализуют интерфейс Iterable<N>, где N - это Int, Long или Char, поэтому вы можете использовать их в различных функциях коллекций, таких как map, filter и т. д.

fun main() {
    println((1..10).filter { it % 2 == 0 }) // [2, 4, 6, 8, 10]
}