Интервалы

Интервалы оформлены с помощью функций rangeTo и имеют оператор в виде .., который дополнятся in и !in. Они применимы ко всем сравниваемым (comparable) типам, но для целочисленных примитивов есть оптимизированная реализация. Вот несколько примеров применения интервалов.

if (i in 1..10) { // equivalent of 1 <= i && i <= 10
    println(i)
}

Интервалы целочисленного типа (IntRange, LongRange, CharRange) имеют определённое преимущество: они могут иметь дополнительную итерацию. Компилятор конвертирует такие интервалы в аналогичные циклы for из языка Java.

for (i in 1..4) print(i) // prints "1234"

for (i in 4..1) print(i) // prints nothing

А что, если вы хотите произвести итерацию в обратном порядке? Это просто. Можете использовать функцию downTo(), определённую в стандартной библиотеке.

for (i in 4 downTo 1) print(i) // prints "4321"

А есть ли возможность производить итерацию с шагом, отличным от еденицы? Разумеется. В этом вам поможет функция step():

for (i in 1..4 step 2) print(i) // prints "13"

for (i in 4 downTo 1 step 2) print(i) // prints "42"

Для создания интервала, который не включает последний элемент перебора, используйте until:

for (i in 1 until 10) { // i in [1, 10), 10 is excluded
     println(i)
}

Как это работает

Интервалы реализуют интерфейс ClosedRange<T>.

Говоря математическим языком, интерфейс ClosedRange<T> обозначет ограниченный отрезок и предназначен для типов, подлежащих сравнению. У него есть две контрольные точки: start и endInclusive. Главной операцией является contain. Чаще всего она используется вместе с операторами in/!in.

Целочисленные последовательности (IntProgression, LongProgression, CharProgression) являются арифметическими. Последовательности определены элементами first, last и ненулевым значением increment. Элемент first является первым, последующими являются элементы, полученные при инкрементации предыдущего элемента с помощью increment. Если последовательность не яаляется пустой, то элемент last всегда достигается в результате инкрементации.

Последовательность является подтипом Iterable<N>, где N - это Int, Long или Char. Таким образом, её можно использовать в циклах for и функциях типа map, filter и т.п. Итерация Progression идентична проиндексованному циклу for в Java/JavaScript

for (int i = first; i != last; i += increment) {
  // ...
}

Для целочисленных типов, оператор .. создаёт объект, который реализует в себе ClosedRange<T> и *Progression*. К примеру, IntRange наследуется от класса IntProgression и реализует интерфейс ClosedRange<Int>. Поэтому все операторы, обозначенные для IntProgression, также доступны и для IntRange. Результатом функций downTo() и step() всегда будет *Progression*(перев.: последовательность).

Последовательности спроектированы с использованием функции fromClosedRange в их вспомогательном объекте (companion object):

    IntProgression.fromClosedRange(start, end, increment)

Для нахождения максимального значения в прогрессии вычисляется элемент last. Для последовательности с положительным инкрементом этот элемент вычисляется так, чтобы он был не больше элемента end. Для тех последовательностей, где инкремент отрицательный - не меньше.

Вспомогательные функции (ориг.: Utility functions)

rangeTo()

Операторы rangeTo() для целочисленных типов просто вызывают конструктор класса *Range*:

class Int {
    //...
    operator fun rangeTo(other: Long): LongRange = LongRange(this, other)
    //...
    operator fun rangeTo(other: Int): IntRange = IntRange(this, other)
    //...
}

Числа с плавающей точкой (Double, Float) не имеют своего оператора rangeTo. Такой оператор обозначен для них в дженериках типа Comparable стандартной библиотеки:

    public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>

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

downTo()

Экстеншн-функция downTo() задана для любой пары целочисленных типов, вот два примера:

fun Long.downTo(other: Int): LongProgression {
    return LongProgression.fromClosedRange(this, other, -1.0)
}

fun Byte.downTo(other: Int): IntProgression {
    return IntProgression.fromClosedRange(this, other, -1)
}

reversed()

Функция reversed() расширяет класс *Progression* таким образом, что все экземпляры этого класса возвращают обратные последовательности при её вызове.

fun IntProgression.reversed(): IntProgression {
    return IntProgression.fromClosedRange(last, first, -increment)
}

step()

Функция-расширение step() также определена для классов *Progression*, возвращает последовательность с изменённым значением шага step (параметр функции). Значение шага всегда должно быть положительным числом для того, чтобы функция никогда не меняла направления своей итерации.

fun IntProgression.step(step: Int): IntProgression {
    if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step") //шаг должен быть положительным
    return IntProgression.fromClosedRange(first, last, if (increment > 0) step else -step)
}

fun CharProgression.step(step: Int): CharProgression {
    if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
    return CharProgression.fromClosedRange(first, last, step)
}

Обратите внимание, что значение элемента last в возвращенной последовательности может отличаться от значения last первоночальной последовательности с тем, чтобы предотвратить инвариант (last - first) % increment == 0. Вот пример:

    (1..12 step 2).last == 11  // последовательность чисел со значениями [1, 3, 5, 7, 9, 11]
    (1..12 step 3).last == 10  // последовательность чисел со значениями [1, 4, 7, 10]
    (1..12 step 4).last == 9   // последовательность чисел со значениями [1, 5, 9]