Зворотний діапазон у Свіфті


105

Чи існує спосіб роботи зі зворотними діапазонами в Swift?

Наприклад:

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

є нескінченною петлею.

У нових версіях Swift цей код компілюється, але під час виконання видає помилку:

Фатальна помилка: не вдається створити діапазон з верхньою границею <нижньою границею

Я знаю, що можу використовувати 1..5замість цього обчислення j = 6 - iта використовувати jяк свій індекс. Мені було просто цікаво, чи є щось більш розбірливе?


Дивіться мою відповідь на подібне запитання, яке дає 8 способів вирішити вашу проблему з Swift 3.
Imanou Petit

Відповіді:


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, по і 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код для швидкого 3 трохи невірний. щоб включити число 1, вам потрібно використовувати throughпротилежне toтак:for i in stride(from:5,through:1,by:-1) { print(i) }
262 Гц

4
Варто зазначити, що reversedвона має складність O (1), оскільки вона просто обгортає оригінальну колекцію і не потребує ітерації для її створення.
devios1

21

У асиметрії цього є щось тривожне:

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

... на відміну від цього:

for i in 1..<5 {

Це означає, що кожного разу, коли я хочу зробити зворотний діапазон, я повинен пам’ятати, що потрібно поставити круглі дужки, плюс я мушу це написати .reverse()в кінці, стирчачи, як біль. Це справді некрасиво порівняно зі стилем С для петель, які є симетричним підрахунком і відліком. Тому я, як правило, використовував С-стиль для циклів. Але в Swift 2.2 відміняється стиль для петель! Тому мені довелося поспішати замінити всі мої декрементирующие стилі 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 насіння 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
Ого, >>>оператор дійсно крутий! Я сподіваюся, що дизайнери Свіфт звертають увагу на подібні речі, тому що дотримуватися reverse()кінця виразів у дужках справді дивно. Немає причин, щоб вони не могли 5>..0автоматично вирішувати діапазони, тому що ForwardIndexTypeпротокол забезпечує цілком розумний distanceTo()оператор, який може бути використаний для з'ясування, чи має бути позитивний чи негативний крок.
dasblinkenlight

1
Дякую @dasblinkenlight - Це придумало мене, оскільки в одному з моїх додатків у мене було багато C для циклів (в Objective-C), які перейшли до циклу Swift C для циклів і тепер його довелося перетворити, оскільки C-стиль для циклів йдуть геть. Це було просто жахливо, тому що ці петлі представляли ядро ​​логіки мого додатка, і переписування всього ризикує поломкою. Таким чином, я хотів позначити, що зробить відповідність нотації (прокоментував) С-стиль якомога чіткіше.
мат

акуратний! Мені подобається ваша одержимість чистим виглядом коду!
Йост

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 видалено; використовувати ледачий (х ..

Ось приклад.

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

+1 Я шукаю посилання на lazy(0....5).reverse()нотатки до випуску бета-5. Чи знаєте ви про інші дискусії на цю тему? Також, FYI, цифри всередині цієї forпетлі є назад у вашому прикладі.
Роб

1
@Rob Хм, я повинен був подумати, що нотатки до випуску бета зрештою будуть зняті. Коли я це писав, це було єдине місце, де я бачив посилання на ледачий (), але я з радістю оглянувся ще дещо та оновив / виправлю це, коли повернусь пізніше додому. Дякуємо, що вказали на це.
Мік Маккаллум

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>посилання


1

Це може бути інший спосіб зробити це.

(1...5).reversed().forEach { print($0) }

-2

Для зворотного числа використовується функція reverse ().

Var n: Int // Введіть номер

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.