Обратный диапазон в Swift

105

Есть ли в Swift возможность работать с обратными диапазонами?

Например:

for i in 5...1 {
  // do something
}

это бесконечный цикл.

В более новых версиях Swift этот код компилируется, но во время выполнения выдает ошибку:

Неустранимая ошибка: не удается сформировать диапазон с верхним пределом <нижний предел

Я знаю, что могу использовать 1..5вместо этого, вычислять j = 6 - iи использовать в jкачестве индекса. Мне просто интересно, есть ли что-нибудь более разборчивое?

Эдуардо
источник
Смотрите мой ответ на аналогичный вопрос, который дает до 8 способов решить вашу проблему с помощью Swift 3.
Иману Пети

Ответы:

184

Обновление для последней версии Swift 3 (все еще работает в Swift 4)

Вы можете использовать reversed()метод в диапазоне

for i in (1...5).reversed() { print(i) } // 5 4 3 2 1

Или stride(from:through:by:)метод

for i in stride(from:5,through:1,by:-1) { print(i) } // 5 4 3 2 1

stide(from:to:by:) аналогично, но исключает последнее значение

for i in stride(from:5,to:0,by:-1) { print(i) } // 5 4 3 2 1

Обновление для последней версии Swift 2

Прежде всего, расширения протокола меняют способ reverseиспользования:

for i in (1...5).reverse() { print(i) } // 5 4 3 2 1

Страйд был переработан в Xcode 7 Beta 6. Новое использование:

for i in 0.stride(to: -8, by: -2) { print(i) } // 0 -2 -4 -6
for i in 0.stride(through: -8, by: -2) { print(i) } // 0 -2 -4 -6 -8

Он также работает для Doubles:

for i in 0.5.stride(to:-0.1, by: -0.1) { print(i) }

Остерегайтесь сравнений с плавающей запятой здесь для определения границ.

Предыдущее изменение для Swift 1.2 : Начиная с Xcode 6 Beta 4, by и ReverseRange больше не существуют: [

Если вы просто хотите изменить диапазон, обратная функция - это все, что вам нужно:

for i in reverse(1...5) { println(i) } // prints 5,4,3,2,1

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

Источник из его ответа:

for x in stride(from: 0, through: -8, by: -2) {
    println(x) // 0, -2, -4, -6, -8
}

for x in stride(from: 6, to: -2, by: -4) {
    println(x) // 6, 2
}
разъем
источник
FYI Stride изменился с момента беты 6
DogCoffee
1
он должен быть (1...5).reverse()(как вызов функции), обновите свой ответ. (Так как это всего два символа, я не мог редактировать ваш пост.)
Бехдад
В Swift 3.0-PREVIEW-4 (и, возможно, ранее) так и должно быть (1...5).reversed(). Кроме того, пример для .stride()не работает, и stride(from:to:by:)вместо него требуется бесплатная функция .
davidA
2
похоже, что strideкод для swift 3 немного неверен. чтобы включить число 1, вам нужно будет использовать throughвместо toэтого:for i in stride(from:5,through:1,by:-1) { print(i) }
262Hz
4
Стоит отметить, что у reversedнего сложность O (1), потому что он просто обертывает исходную коллекцию и не требует итерации для ее создания.
devios1
21

В асимметрии этого есть что-то тревожное:

for i in (1..<5).reverse()

... в отличие от этого:

for i in 1..<5 {

Это означает, что каждый раз, когда я хочу сделать обратный диапазон, я должен не забыть поставить круглые скобки, плюс я должен написать это .reverse()в конце, торча, как больной палец. Это действительно некрасиво по сравнению с C-стилем для циклов, которые симметричны, считая вверх и вниз. Поэтому я предпочел использовать C-стиль для циклов. Но в Swift 2.2 цикл for в стиле C уходит! Поэтому мне пришлось поспешно заменить все мои декрементирующие циклы for в C-стиле этой уродливой .reverse()конструкцией - все время задаваясь вопросом, почему, черт возьми, нет оператора обратного диапазона?

Но ждать! Это Swift - нам разрешено определять собственные операторы !! Вот так:

infix operator >>> {
    associativity none
    precedence 135
}

func >>> <Pos : ForwardIndexType where Pos : Comparable>(end:Pos, start:Pos)
    -> ReverseRandomAccessCollection<(Range<Pos>)> {
        return (start..<end).reverse()
}

Итак, теперь я могу сказать:

for i in 5>>>1 {print(i)} // 4, 3, 2, 1

Это охватывает только наиболее распространенный случай , что происходит в моем коде, но это , несомненно, самый распространенный случай, так что это все нужно , я в настоящее время .

У меня был какой-то внутренний кризис с оператором. Я бы хотел использовать >.., как обратное ..<, но это незаконно: похоже, вы не можете использовать точку после точки, не являющейся точкой. Я подумал, ..>но решил, что это слишком сложно отличить ..<. Приятно то, >>>что он кричит на вас: « вниз !» (Конечно, вы можете придумать другого оператора. Но мой совет: для суперсимметрии определите <<<, что делать ..<, и теперь у вас есть <<<и>>> . , Которые симметричны и легко печатать)


Версия Swift 3 (Xcode 8 seed 6):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound) ->
    ReversedRandomAccessCollection<CountableRange<Bound>> 
    where Bound : Comparable, Bound.Stride : Integer {
        return (minimum..<maximum).reversed()
}

Версия Swift 4 (Xcode 9 beta 3):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedRandomAccessCollection<CountableRange<Bound>>
    where Bound : Comparable & Strideable { 
        return (minimum..<maximum).reversed()
}

Версия Swift 4.2 (Xcode 10 beta 1):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedRandomAccessCollection<Range<Bound>>
    where Bound : Strideable { 
        return (minimum..<maximum).reversed()
}

Версия Swift 5 (Xcode 10.2.1):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedCollection<Range<Bound>>
    where Bound : Strideable {
        return (minimum..<maximum).reversed()
}
матовый
источник
1
Ух, >>>оператор действительно классный! Я надеюсь, что дизайнеры Swift обратят внимание на подобные вещи, потому что придерживаться reverse()конца выражений в скобках кажется странным. Нет никаких причин, по которым они не могли обрабатывать 5>..0диапазоны автоматически, потому что ForwardIndexTypeпротокол предоставляет вполне разумный distanceTo()оператор, который можно использовать для определения того, должен ли шаг быть положительным или отрицательным.
dasblinkenlight
1
Спасибо @dasblinkenlight - это пришло мне в голову, потому что в одном из моих приложений у меня было много циклов C for (в Objective-C), которые перешли на Swift C-style для циклов и теперь должны были быть преобразованы, потому что C-style for loops уезжают. Это было просто ужасно, потому что эти циклы представляли собой ядро ​​логики моего приложения, а переписывание всего чревато поломкой. Таким образом, мне нужна была нотация, которая делала бы соответствие с (закомментированной) нотацией C-стиля как можно более ясной.
Мэтт
аккуратно! Мне нравится твоя одержимость чистым кодом!
Yohst
10

Похоже, что ответы на этот вопрос немного изменились по мере прохождения бета-тестирования. Начиная с бета-версии 4, и by()функция, и ReversedRangeтип были удалены из языка. Если вы хотите сделать обратный диапазон, теперь у вас есть следующие варианты:

1: Создайте прямой диапазон, а затем используйте reverse()функцию, чтобы перевернуть его.

for x in reverse(0 ... 4) {
    println(x) // 4, 3, 2, 1, 0
}

for x in reverse(0 ..< 4) {
    println(x) // 3, 2, 1, 0
}

2: Используйте новые stride()функции, которые были добавлены в бета-версии 4, которая включает функции для указания начального и конечного индексов, а также количества для итерации.

for x in stride(from: 0, through: -8, by: -2) {
    println(x) // 0, -2, -4, -6, -8
}

for x in stride(from: 6, to: -2, by: -4) {
    println(x) // 6, 2
}

Обратите внимание, что я также включил в этот пост новый оператор исключительного диапазона. ..был заменен на ..<.

Изменить: из примечаний к выпуску Xcode 6 beta 5 Apple добавила следующее предложение для решения этой проблемы:

ReverseRange был удален; использовать ленивый (x ..

Вот вам пример.

for i in lazy(0...5).reverse() {
    // 0, 1, 2, 3, 4, 5
}
Мик МакКаллум
источник
+1 Я ищу ссылку lazy(0....5).reverse()и не вижу примечания к выпуску бета 5. Знаете ли вы о других обсуждениях по этой теме? Кроме того, к вашему сведению, числа внутри этого forцикла в вашем примере указаны наоборот.
Роб
1
@Rob Хм, я должен был догадаться, что примечания к выпуску бета-версии в конечном итоге будут удалены. Когда я писал это, это было единственное место, где я видел ссылку на lazy (), но я с радостью посмотрю вокруг и обновлю / исправлю это, когда вернусь домой позже. Спасибо за указание на это.
Mick MacCallum
1
@ 0x7fffffff В вашем последнем примере в комментарии говорится, // 0, 1, 2, 3, 4, 5но на самом деле его нужно поменять местами. Комментарий вводит в заблуждение.
Панг
5

Xcode 7, бета 2:

for i in (1...5).reverse() {
  // do something
}
Лэнн
источник
3

Swift 3, 4+ : это можно сделать так:

for i in sequence(first: 10, next: {$0 - 1}) {

    guard i >= 0 else {
        break
    }
    print(i)
}

результат : 10, 9, 8 ... 0

Вы можете настроить его как хотите. Для получения дополнительной информации прочтите func sequence<T>ссылку

nCod3d
источник
1

Это может быть другой способ сделать это.

(1...5).reversed().forEach { print($0) }
Дэвид Х
источник
-2

Функция Reverse () используется для обратного числа.

Var n: Int // Введите число

Для i в 1 ... n.reverse () {Print (i)}

Laxrajpurohit
источник