Avatar ·

Как создать диапазон (range) в Kotlin, который считает в обратном порядке (от большего к меньшему)?

Привет, коллеги! Я недавно начал изучать Kotlin и столкнулся с проблемой при работе с диапазонами (ranges). Мне нужно перебрать числа от 10 до 1 в цикле for, то есть в обратном порядке. Я попробовал написать так: ```kotlin for (i in 10..1) { println(i) } ``` Но этот код ничего не выводит! Я ожидал увидеть числа 10, 9, 8... 1, но цикл просто не выполняется. Я читал документацию и понял, что `10..1` создаёт пустой диапазон, потому что шаг по умолчанию — +1, а начальное значение больше конечного. Я пробовал использовать `downTo`: ```kotlin for (i in 10 downTo 1) { println(i) } ``` Это сработало! Но у меня есть несколько вопросов: 1. Почему `10..1` не работает, а `10 downTo 1` работает? 2. Можно ли как-то создать переменную-диапазон, которая хранит обратную последовательность, чтобы потом использовать её несколько раз? 3. Есть ли другие способы сделать обратный перебор, например, через `reversed()` или `step`? Объясните, пожалуйста, как правильно работать с обратными диапазонами в Kotlin, и какие есть подводные камни. Заранее спасибо! <<>>

Почему 10..1 не работает?

В Kotlin оператор .. всегда создаёт диапазон с шагом +1. Если начальное значение больше конечного, результатом будет пустой диапазон (IntRange), так как компилятор не может автоматически изменить направление. Это сделано для безопасности — вы явно должны указать, что хотите обратный порядок.

Правильный способ: downTo

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

// Базовый пример
for (i in 10 downTo 1) {
    println(i) // 10, 9, 8... 1
}

// С шагом больше 1
for (i in 10 downTo 1 step 2) {
    println(i) // 10, 8, 6, 4, 2
}

// Сохраняем в переменную
val reverseRange: IntProgression = 10 downTo 1
for (i in reverseRange) {
    println(i)
}
// Можно использовать несколько раз
println("Ещё раз:")
for (i in reverseRange) {
    println(i)
}

Важные детали про IntProgression

Когда вы пишете 10 downTo 1, вы создаёте не IntRange, а IntProgression (прогрессию). В отличие от диапазона (IntRange), прогрессия может иметь отрицательный шаг. Вот ключевые отличия:

  • IntRange — всегда возрастает, шаг = +1. Создаётся через ...
  • IntProgression — может убывать, шаг может быть любым. Создаётся через downTo или step.

Альтернативные подходы

1. Использование reversed()
Если у вас уже есть возрастающий диапазон, можно развернуть его:

val ascending = 1..10
for (i in ascending.reversed()) {
    println(i) // 10, 9, 8... 1
}
// Или в одну строку:
for (i in (1..10).reversed()) {
    println(i)
}

2. Использование until с downTo
Если нужно исключить последнее значение:

for (i in 10 downTo 1) {
    // включает 1
}
for (i in 10 downTo 2) {
    // не включает 1
}

Подводные камни

  • Пустой диапазон: for (i in 5..3) — ничего не выведет. Всегда используйте downTo для обратного порядка.
  • Тип переменной: val r = 10 downTo 1 — тип IntProgression, а не IntRange. Это важно при передаче в функции.
  • Шаг должен быть положительным: 10 downTo 1 step -2 — вызовет ошибку компиляции. Шаг всегда указывается как положительное число, а направление задаётся downTo.
  • Производительность: reversed() создаёт новый объект-прогрессию, но это незначительные затраты для большинства случаев.

Итог

Для обратного перебора всегда используйте downTo. Это идиоматичный и эффективный способ в Kotlin. Если нужно сохранить диапазон для многократного использования — присвойте результат downTo переменной типа IntProgression. Избегайте оператора .. для обратных последовательностей, так как он создаёт пустой диапазон.

<<>>

Дополнительный способ: цикл while

Хотя downTo — предпочтительный вариант, иногда полезно знать альтернативы. Например, можно использовать классический цикл while:

var i = 10
while (i >= 1) {
    println(i)
    i--
}
// Или с декрементом
var j = 10
while (j >= 1) {
    println(j)
    j -= 1
}

Использование библиотечных функций

Для коллекций можно применить forEach:

(10 downTo 1).forEach { println(it) }
// Или через индексы:
val list = listOf("a", "b", "c")
for (i in list.indices.reversed()) {
    println("${list[i]} на позиции $i")
}

Когда это может пригодиться?

  • Если вам нужно больше контроля над итерацией (например, изменять шаг динамически).
  • При работе с коллекциями, где indices.reversed() даёт понятный код.
  • В случаях, когда нужно прервать цикл по условию — while даёт больше гибкости.

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