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?

Eduardo
źródło
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
}
Jacek
źródło
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()
}
matowe
źródło
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
}
Mick MacCallum
źródło
+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
}
leanne
źródło
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

nCod3d
źródło
1

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

(1...5).reversed().forEach { print($0) }
David H.
źródło
-2

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

Var n: Int // Wpisz liczbę

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

laxrajpurohit
źródło