Odwróć zakres w Swift


105

Czy istnieje sposób pracy z odwróconymi zakresami w Swift?

Na przykład:

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

jest nieskończoną pętlą.

W nowszych wersjach Swifta ten kod kompiluje się, ale w czasie wykonywania wyświetla błąd:

Błąd krytyczny: nie można utworzyć zakresu z upperBound <lowerBound

Wiem, że mogę 1..5zamiast tego używać , obliczać j = 6 - ii używać jjako mojego indeksu. Zastanawiałem się tylko, czy jest coś bardziej czytelnego?


Zobacz moją odpowiedź na podobne pytanie, która daje 8 sposobów rozwiązania problemu w Swift 3.
— Imanou Petit,

Odpowiedzi:


184

Aktualizacja dla najnowszego Swift 3 (nadal działa w Swift 4)

Możesz użyć tej reversed()metody na zakresie

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

Albo stride(from:through:by:)metoda

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

stide(from:to:by:) jest podobny, ale wyklucza ostatnią wartość

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

Aktualizacja dla najnowszego Swift 2

Przede wszystkim rozszerzenia protokołów zmieniają sposób reverseużycia:

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

Stride został przerobiony w Xcode 7 Beta 6. Nowe użycie to:

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

Działa również w przypadku Doubles:

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

Uważaj na porównania zmiennoprzecinkowe tutaj dla granic.

Wcześniejsza edycja dla Swift 1.2 : od Xcode 6 Beta 4, by i ReverseRange już nie istnieją: [

Jeśli chcesz tylko odwrócić zakres, wystarczy funkcja reverse :

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

Jak napisano w 0x7fffffff, istnieje nowa konstrukcja stride, której można użyć do iteracji i inkrementacji przez dowolne liczby całkowite. Apple stwierdził również, że nadchodzi obsługa zmiennoprzecinkowych.

Pochodzi z jego odpowiedzi:

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 zmieniło się od wersji beta 6
— DogCoffee

1
powinno być (1...5).reverse()(jako wywołanie funkcji), zaktualizuj swoją odpowiedź. (Ponieważ to tylko dwie postacie, nie mogłem edytować twojego postu.)
— Behdad

W Swift 3.0-PREVIEW-4 (i być może wcześniejszym) tak powinno być (1...5).reversed(). Ponadto przykład dla .stride()nie działa i stride(from:to:by:)zamiast tego potrzebuje bezpłatnej funkcji .
— davidA

2
wygląda na to, że stridekod języka Swift 3 jest nieco niepoprawny. aby uwzględnić liczbę 1, należy użyć w throughprzeciwieństwie do totego:for i in stride(from:5,through:1,by:-1) { print(i) }
— 262 Hz

4
Warto zauważyć, że reversedma złożoność O (1), ponieważ po prostu otacza oryginalną kolekcję i nie wymaga iteracji, aby ją zbudować.
— devios1

21

Jest coś niepokojącego w asymetrii tego:

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

... w przeciwieństwie do tego:

for i in 1..<5 {

Oznacza to, że za każdym razem, gdy chcę zrobić odwrócony zakres, muszę pamiętać o umieszczeniu nawiasów, a dodatkowo muszę to napisać .reverse()na końcu, wystając jak bolący kciuk. Jest to naprawdę brzydkie w porównaniu do pętli w stylu C, które są symetryczne zliczaniem w górę i w dół. Więc zamiast tego używałam pętli w stylu C. Ale w Swift 2.2, pętle w stylu C odchodzą! Więc musiałem się pospieszyć, zastępując wszystkie moje dekrementujące pętle w stylu C dla tej brzydkiej .reverse()konstrukcji - zastanawiając się przez cały czas, dlaczego u licha nie ma operatora odwrotnego zasięgu?

Ale poczekaj! To jest Swift - wolno nam definiować własnych operatorów !! No to ruszamy:

infix operator >>> {
    associativity none
    precedence 135
}

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

Więc teraz mogę powiedzieć:

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

Obejmuje to tylko najczęstszy przypadek, który występuje w moim kodzie, ale jest to zdecydowanie najczęstszy przypadek, więc to wszystko, czego obecnie potrzebuję.

Miałem pewnego rodzaju wewnętrzny kryzys, który zbliżał się do operatora. Chciałbym użyć >.., jako odwrotności ..<, ale to nie jest legalne: wydaje się, że nie można używać kropki po nie-kropce. Rozważałem, ..>ale zdecydowałem, że to zbyt trudne do odróżnienia ..<. Fajną rzeczą >>>jest to, że krzyczy na ciebie: „na dół !” (Oczywiście możesz wymyślić inny operator. Ale moja rada jest taka: aby uzyskać super symetrię, zdefiniuj <<<, co ..<robi, a teraz masz <<<i >>>które są symetryczne i łatwe do wpisania.)


Wersja 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()
}

Wersja 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()
}

Wersja 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()
}

Wersja 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
Wow, >>>operator jest naprawdę fajny! Mam nadzieję, że projektanci Swift zwracają uwagę na takie rzeczy, ponieważ trzymanie reverse()się końcówek wyrażeń w nawiasach wydaje się dziwne. Nie ma powodu, dla którego nie mogliby 5>..0automatycznie zajmować się zakresami, ponieważ ForwardIndexTypeprotokół zapewnia całkowicie rozsądny distanceTo()operator, którego można użyć do ustalenia, czy krok powinien być dodatni czy ujemny.
— dasblinkenlight

1
Dzięki @dasblinkenlight - Pojawiło się to dla mnie, ponieważ w jednej z moich aplikacji miałem dużo pętli C for (w Objective-C), które migrowały do ​​pętli w stylu Swift C i teraz musiałem zostać przekonwertowane, ponieważ pętle w stylu C odchodzą. To było po prostu przerażające, ponieważ te pętle stanowiły rdzeń logiki mojej aplikacji, a przepisywanie wszystkiego groziło zerwaniem. Dlatego chciałem notacji, która sprawiłaby, że korespondencja z (zakomentowaną) notacją w stylu C byłaby tak jasna, jak to tylko możliwe.
— mat.

schludny! Podoba mi się twoja obsesja na punkcie czysto wyglądającego kodu!
— Yohst

10

Wygląda na to, że odpowiedzi na to pytanie nieco się zmieniły, gdy przechodziliśmy przez wersje beta. Od wersji beta 4 zarówno by()funkcja , jak i ReversedRangetyp zostały usunięte z języka. Jeśli chcesz uzyskać odwrócony zakres, masz teraz następujące opcje:

1: Utwórz zakres do przodu, a następnie użyj reverse()funkcji, aby go odwrócić.

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: Skorzystaj z nowych stride()funkcji, które zostały dodane w wersji beta 4, w tym funkcji do określania indeksów początkowych i końcowych, a także ilości do iteracji.

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
}

Zauważ, że w tym poście umieściłem również nowego operatora zakresu wyłącznego. ..został zastąpiony ..<.

Edycja: z informacji o wersji Xcode 6 beta 5 firma Apple dodała następującą sugestię dotyczącą rozwiązania tego problemu:

ReverseRange został usunięty; użyj leniwego (x ..

Oto przykład.

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

+1 Szukam odniesienia lazy(0....5).reverse()i nie widzę informacji o wersji beta 5. Czy znasz inne dyskusje na ten temat? Ponadto, do Twojej wiadomości, liczby w tej forpętli są w twoim przykładzie odwrócone.
— Rob

1
@Rob Hmm Powinienem był się domyślić, że informacje o wydaniu wersji beta zostaną w końcu usunięte. Kiedy to pisałem, było to jedyne miejsce, w którym widziałem odniesienie do lazy (), ale chętnie rozejrzę się po więcej i zaktualizuję / poprawię to, gdy wrócę do domu później. Dziękuję za zwrócenie uwagi.
— Mick MacCallum

1
@ 0x7fffffff W twoim ostatnim przykładzie komentarz mówi, // 0, 1, 2, 3, 4, 5ale w rzeczywistości powinien zostać odwrócony. Komentarz jest mylący.
— Pang

5

Xcode 7, beta 2:

for i in (1...5).reverse() {
  // do something
}

3

Swift 3, 4+ : możesz to zrobić w ten sposób:

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

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

wynik : 10, 9, 8 ... 0

Możesz go dostosować w dowolny sposób. Aby uzyskać więcej informacji, przeczytaj func sequence<T>odniesienie


1

To mógłby być inny sposób na zrobienie tego.

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

-2

Funkcja Reverse () służy do odwrócenia liczby.

Var n: Int // Wpisz liczbę

For i in 1 ... n.reverse () {Print (i)}

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.