Intervalo reverso em Swift

105

Existe uma maneira de trabalhar com intervalos reversos em Swift?

Por exemplo:

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

é um loop infinito.

Em versões mais recentes do Swift, esse código é compilado, mas em tempo de execução dá o erro:

Erro fatal: não é possível formar Range com upperBound <lowerBound

Eu sei que posso usar em 1..5vez disso, calcular j = 6 - ie usar jcomo meu índice. Eu só estava me perguntando se havia algo mais legível.

Eduardo
fonte
Veja minha resposta para uma pergunta semelhante que fornece até 8 maneiras de resolver seu problema com o Swift 3.
Imanou Petit

Respostas:

184

Atualizar para o Swift 3 mais recente (ainda funciona no Swift 4)

Você pode usar o reversed()método em um intervalo

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

Ou stride(from:through:by:)método

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

stide(from:to:by:) é semelhante, mas exclui o último valor

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

Atualização para o último Swift 2

Em primeiro lugar, as extensões de protocolo mudam a forma como reverseé usado:

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

Stride foi retrabalhado no Xcode 7 Beta 6. O novo uso é:

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

Também funciona para Doubles:

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

Tenha cuidado com as comparações de ponto flutuante aqui para os limites.

Edição anterior para Swift 1.2 : a partir do Xcode 6 Beta 4, por e ReverseRange não existem mais: [

Se você está apenas procurando inverter um intervalo, a função reversa é tudo que você precisa:

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

Conforme postado por 0x7fffffff, há uma nova construção de passo que pode ser usada para iterar e incrementar por inteiros arbitrários. A Apple também afirmou que o suporte de ponto flutuante está chegando.

Proveniente de sua resposta:

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
}
Jack
fonte
FYI Stride mudou desde beta 6
DogCoffee
1
deveria ser (1...5).reverse()(como uma chamada de função), atualize sua resposta. (Como são apenas dois caracteres, não consegui editar sua postagem.)
Behdad
No Swift 3.0-PREVIEW-4 (e possivelmente anterior), deveria ser (1...5).reversed(). Além disso, o exemplo para .stride()não funciona e, em stride(from:to:by:)vez disso , precisa da função free .
davidA
2
parece que o stridecódigo do swift 3 está ligeiramente incorreto. para incluir o número 1, você precisaria usar throughoposto a toeste:for i in stride(from:5,through:1,by:-1) { print(i) }
262 Hz
4
Vale ressaltar que reversedpossui uma complexidade de O (1) porque apenas envolve a coleção original e não requer uma iteração para construí-la.
devios1
21

Há algo preocupante sobre a assimetria disso:

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

... em oposição a este:

for i in 1..<5 {

Isso significa que toda vez que eu quiser fazer um intervalo reverso, tenho que me lembrar de colocar os parênteses, além disso, tenho que escrever isso .reverse()no final, saindo como um polegar machucado. Isso é realmente feio em comparação com os loops for do estilo C, que são simétricos em contagem crescente e decrescente. Portanto, eu tendia a usar loops for no estilo C. Mas no Swift 2.2, os loops for estilo C estão desaparecendo! Então tive que me apressar para substituir todos os meus loops for de estilo C decrescentes por esta .reverse()construção feia - me perguntando o tempo todo, por que diabos não existe um operador de reversão?

Mas espere! Este é o Swift - temos permissão para definir nossos próprios operadores !! Aqui vamos nós:

infix operator >>> {
    associativity none
    precedence 135
}

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

Agora posso dizer:

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

Isso cobre apenas o caso mais comum que ocorre em meu código, mas é de longe o caso mais comum, então é tudo de que preciso no momento.

Tive uma espécie de crise interna chegando com a operadora. Eu gostaria de usar >.., como sendo o reverso de ..<, mas isso não é legal: você não pode usar um ponto após um não-ponto, parece. Eu considerei, ..>mas decidi que era muito difícil distinguir ..<. O bom >>>disso é que grita com você: " até !" (Claro que você está livre para sugerir outro operador. Mas meu conselho é: para supersimetria, defina o <<<que ..<faz e agora você tem <<<e >>>quais são simétricos e fáceis de digitar.)


Versão 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()
}

Versão 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()
}

Versão 4.2 do Swift (Xcode 10 beta 1):

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

Versão do 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()
}
mate
fonte
1
Nossa, a >>>operadora é muito legal! Espero que os designers do Swift prestem atenção a coisas como essa, porque colar reverse()no final das expressões entre parênteses parece estranho. Não há razão para que eles não possam lidar com os 5>..0intervalos automaticamente, porque o ForwardIndexTypeprotocolo fornece um distanceTo()operador perfeitamente razoável que pode ser usado para descobrir se o passo deve ser positivo ou negativo.
dasblinkenlight
1
Obrigado @dasblinkenlight - Isso surgiu para mim porque em um dos meus aplicativos eu tinha muitos loops for C (em Objective-C) que migraram para loops for Swift C-style e agora tiveram que ser convertidos porque loops for loops C-style estão indo embora. Isso era simplesmente assustador, porque esses loops representavam o núcleo da lógica do meu aplicativo, e reescrever tudo corria o risco de quebrar. Portanto, eu queria uma notação que tornasse a correspondência com a notação de estilo C (comentada) o mais clara possível.
Mateus
arrumado! Eu gosto da sua obsessão com código de aparência limpa!
Yohst
10

Parece que as respostas a essa pergunta mudaram um pouco à medida que avançamos nos betas. No beta 4, tanto a by()função quanto o ReversedRangetipo foram removidos do idioma. Se você deseja fazer um intervalo invertido, suas opções agora são as seguintes:

1: Crie um intervalo de avanço e, em seguida, use a reverse()função para invertê-lo.

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: Use as novas stride()funções que foram adicionadas no beta 4, que inclui funções para especificar os índices inicial e final, bem como a quantidade de iteração.

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
}

Observe que também incluí o novo operador de alcance exclusivo neste artigo. ..foi substituído por ..<.

Edit: A partir das notas de lançamento do Xcode 6 beta 5 , a Apple adicionou a seguinte sugestão para lidar com isso:

ReverseRange foi removido; usar preguiçoso (x ..

Aqui está um exemplo.

for i in lazy(0...5).reverse() {
    // 0, 1, 2, 3, 4, 5
}
Mick MacCallum
fonte
1 Estou procurando uma referência lazy(0....5).reverse()e não vejo as notas de lançamento do beta 5. Você conhece outras discussões sobre este assunto? Além disso, FYI, os números dentro desse forloop estão ao contrário em seu exemplo.
Rob de
1
@Rob Hmm, eu deveria ter imaginado que as notas de lançamento de um beta acabariam sendo retiradas. Quando escrevi isso, foi o único lugar em que vi uma referência a lazy (), mas ficarei feliz em procurar mais informações e atualizar / corrigir isso quando voltar para casa mais tarde. Obrigado por apontar isso.
Mick MacCallum
1
@ 0x7fffffff Em seu último exemplo, o comentário diz, // 0, 1, 2, 3, 4, 5mas na verdade deveria ser revertido. O comentário é enganoso.
Pang
5

Xcode 7, beta 2:

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

Swift 3, 4+ : você pode fazer assim:

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

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

resultado : 10, 9, 8 ... 0

Você pode personalizá-lo da maneira que quiser. Para mais informações, leia a func sequence<T>referência

nCod3d
fonte
1

Esta poderia ser outra maneira de fazer isso.

(1...5).reversed().forEach { print($0) }
David H
fonte
-2

A função Reverse () é usada para o número reverso.

Var n: Int // Insira o número

Para i em 1 ... n.reverse () {Imprimir (i)}

laxrajpurohit
fonte