Swift中的反向范围


105

有没有办法在Swift中使用反向范围?

例如:

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

是一个无限循环。

在较新版本的Swift中,该代码可以编译,但在运行时会出现错误:

致命错误:无法使用upperBound <lowerBound形成Range

我知道我可以1..5代替使用计算j = 6 - ij用作索引。我只是想知道是否还有更清晰的地方?


我的回答了类似的问题,让多达8点的方式与斯威夫特3.解决你的问题
Imanou佩蒂特

Answers:


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

Xide 7 Beta 6中对Stride进行了重新设计。新用法是:

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开始,byReverseRange不再存在:[

如果您只是想反转范围,则只需使用反转功能即可:

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

0x7fffffff所发布的那样,有一个新的跨步构造可用于迭代和递增任意整数。苹果还表示,浮点支持即将到来。

来自他的回答:

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
}

自测试版6开始,FYI大步改变
DogCoffee 2015年

1
应该是(1...5).reverse()(作为函数调用),请更新您的答案。(由于只有两个字符,因此我无法编辑您的帖子。)
Behdad

在Swift 3.0-PREVIEW-4(可能更早)中,它应该为(1...5).reversed()。此外,该示例的示例.stride()不起作用,而需要使用free函数stride(from:to:by:)
davidA

2
看来strideswift 3 的代码有点不正确。要包含数字1,则需要使用through相反的tofor i in stride(from:5,through:1,by:-1) { print(i) }
符号

4
值得指出的是,reversed它的复杂度为O(1),因为它只包装原始集合,不需要迭代即可构建它。
devios1

21

这种不对称性令人不安:

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

...与此相反:

for i in 1..<5 {

这意味着每次我想做一个反向范围时,我都必须记住加上括号,并且必须.reverse()在最后写上括号,像拇指一样伸出来。与C样式的for循环(对称地递增和递减)相比,这确实很丑陋。因此,我倾向于使用C样式进行循环。但是在Swift 2.2中,C风格的for循环将消失!因此,我不得不急于用这种丑陋的.reverse()结构替换所有递减的C风格的循环代码-一直在想,为什么地球上没有反向范围运算符?

可是等等!这是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
哇,>>>操作员真的很棒!我希望Swift设计师能注意这样的事情,因为坚持带reverse()括号的表达式的确会感到很奇怪。他们没有理由不能5>..0自动处理范围,因为ForwardIndexType协议提供了一个完全合理的distanceTo()运算符,可用于确定跨步是正数还是负数。
dasblinkenlight

1
谢谢@dasblinkenlight-这对我来说很重要,因为在我的一个应用程序中,我有很多 C for循环(在Objective-C中),已迁移到Swift C样式的for循环,现在不得不转换,因为C样式的for循环要走了。这简直太可怕了,因为这些循环代表了我应用程序逻辑的核心,并且重写所有内容都有损坏的风险。因此,我想要一个符号,使与(注释掉的)C风格符号的对应关系尽可能清晰。
马特

整齐!我喜欢您对简洁代码的痴迷!
约斯特(Yohst)'18年

10

随着我们逐步通过测试版,该问题的答案似乎有所改变。从beta 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()beta 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已被删除;使用lazy(x ..

这是一个例子。

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

+1我正在寻找参考,lazy(0....5).reverse()但看不到Beta 5发行说明。您知道有关此主题的其他讨论吗?同样,仅供参考,for在您的示例中,循环内的数字是向后的。
罗布2014年

1
@Rob Hmm我应该已经知道,一个Beta的发行说明最终会被删除。当我写这篇文章时,那是我唯一看到过lazy()引用的地方,但是我很乐意环顾四周,并在以后回家时进行更新/更正。感谢您指出了这一点。
Mick MacCallum 2014年

1
@ 0x7fffffff在您的最后一个示例中,注释说,// 0, 1, 2, 3, 4, 5但是实际上应该将其反转。该评论具有误导性。
2015年

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 //输入数字

对于1中的i ... n.reverse(){打印(i)}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.