在 swift 中添加对范围运算符的支持? (例如,对于红色...紫色的 x)

Adding support for the range operator in swift? (e.g. for x in red...violet)

在Swift中,我们可以使用range operators.....<)循环范围:

for i in 0...4 { ... }

假设我有很多颜色:

let red: MyColor
let orange: MyColor
let yellow: MyColor
let green: MyColor
let blue: MyColor
let indigo: MyColor
let violet: MyColor

let black: MyColor
let brown: MyColor

如何支持范围运算符以便我可以按彩虹顺序遍历颜色?

for rainbowColor in red...violet { ... }

所以...您可以对任何类型执行 for-in 循环。您所要做的就是实现符合 SequenceType 的东西。然后,一旦您实现了 class 或结构,它就像创建一个 ... 运算符一样简单 return 是 SequenceType.

假设如下:

struct RainbowColor {
    let name: String
}

let red: RainbowColor = RainbowColor(name: "red")
let orange: RainbowColor = RainbowColor(name: "orange")
let yellow: RainbowColor = RainbowColor(name: "yellow")
let green: RainbowColor = RainbowColor(name: "green")
let blue: RainbowColor = RainbowColor(name: "blue")
let indigo: RainbowColor = RainbowColor(name: "indigo")
let violet: RainbowColor = RainbowColor(name: "violet")

我们可以这样创建一个RainbowColorSequence

struct RainbowColorSequence: SequenceType {

    let startColor: RainbowColor
    let endColor: RainbowColor

    init(start: RainbowColor, end: RainbowColor) {
        startColor = start
        endColor = end
    }

    func generate() -> AnyGenerator<RainbowColor> {
        return AnyGenerator<RainbowColor> {
            // TODO: Implement your generator logic.
            return nil
        }
    }

}

(请注意,此处省略了 return 中颜色的顺序以及序列结束时间的实际逻辑......因为它会很混乱。阅读底部以获取示例然而,这个实现可能是什么样子。)

一旦我们有了这个 RainbowColorSequence... 函数就非常简单了:

func ...(lhs: RainbowColor, rhs: RainbowColor) -> RainbowColorSequence {
    return RainbowColorSequence(start: lhs, end: rhs)
}

现在,您可以编写以下代码:

for color in red...violet {
    print(color.name)
}

有关生成器逻辑的示例,请查看 this question。我可以很容易地在 link 后面的代码中添加一个 ... 运算符,因为所有其他逻辑都已经到位,并且 ... 实际上只是围绕SequenceType.

重要的是,您必须自己编写序列的逻辑。鉴于您的条件,编译器没有简单的方法来推断序列中的内容或序列的顺序。并且该逻辑可能会变得非常混乱,具体取决于您想要迭代的类型。

(注意这里是Swift3,如果要Swift2.2,看Senseful的回答,和老协议基本一样。)

您希望 Color 成为 StrideableStrideInt。然后你就可以免费获得 .....<。让我们这样做吧。

首先,我假设您的类型是这样的。这只是一个例子。你可以做所有 的事情,只要你能实现 Strideable。你似乎想要结构而不是枚举(这很好),所以让我们做结构吧。这只是意味着任何人都可以添加其他颜色,您必须在设计中考虑到这一点。颜色需要一些东西来区分它们,所以我们会给它们一个名字。如果他们是类,那也可以区分他们。

struct Color {
    let name: String

    static let red = Color(name: "red")
    static let orange = Color(name: "orange")
    static let yellow = Color(name: "yellow")
    static let green = Color(name: "green")
    static let blue = Color(name: "blue")
    static let indigo = Color(name: "indigo")
    static let violet = Color(name: "violet")
}

Strideable 的第一条规则是 Equatable。

extension Color: Equatable {
    static func == (lhs: Color, rhs: Color) -> Bool {
        return lhs.name == rhs.name
    }
}

现在可以等同了,我们应该想出我们所说的 "order." 的意思 这是一种方法。由于结构使意想不到的值成为可能,我已经说过,在我的任何彩虹色之前,它们都是相等的和有序的。如果这是一个枚举,我们就不必担心这种情况。

extension Color {
    private static let _order: [Color] = [red, orange, yellow, green, blue, indigo, violet]

    private var _indexValue: Int {
        return Color._order.index(of: self) ?? Int.min
    }
}

因此,我们可以回答问题 #2:可比较:

extension Color: Comparable {
    static func < (lhs: Color, rhs: Color) -> Bool {
        return lhs._indexValue < rhs._indexValue
    }
}

迈向 Strideable 仅需一小步:

extension Color: Strideable {
    func advanced(by n: Int) -> Color {
        return Color._order[self._indexValue + n]
    }

    func distance(to other: Color) -> Int {
        return other._indexValue - _indexValue
    }
}

还有你的 ...:

for rainbowColor: Color in .red ... .violet { print(rainbowColor) }

现在我碰巧很喜欢枚举,如果你试图用枚举来实现它,它们恰好有一点技巧,所以值得注意。例如,假设您有:

enum Color {
    case red
    case orange
    case yellow
    case green
    case blue
    case indigo
    case violet
}

原来你不能用index(of:)来计算_indexValue。您会陷入无限循环,因为它在默认 == 实现中调用 distance(to:)(我实际上不确定为什么会发生这种情况)。所以你必须这样实现_indexValue

private var _indexValue: Int {
    switch self {
    case .red: return 0
    case .orange: return 1
    case .yellow: return 2
    case .green: return 3
    case .blue: return 4
    case .indigo: return 5
    case .violet: return 6
    }
}

嗯;这看起来非常像原始值。是的。整个方法基本上是从侧面附加原始值,而无需修改类型本身。所以这就是为什么它看起来真的很相似。其余部分与结构相同,并且该方法可以应用于您可以弄清楚如何跨步的任何其他内容。

(注意:这是针对 Swift 2.2 的。如果您想要 Swift 3 版本,请参阅 。)

Swift 已经定义了以下函数:

/// Forms a closed range that contains both `start` and `end`.
/// - Requires: `start <= end`.
@warn_unused_result
public func ...<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos) -> Range<Pos>

/// Forms a closed range that contains both `minimum` and `maximum`.
@warn_unused_result
public func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos>

/// Returns a closed interval from `start` through `end`.
@warn_unused_result
public func ...<Bound : Comparable>(start: Bound, end: Bound) -> ClosedInterval<Bound>

这意味着我们真正需要做的就是符合 ForwardIndexType。正如 Rob 提到的,它不适用于 Comparable 协议,因为 returns 一个间隔,我们需要一个 for/in 循环的范围。

假设这是我们的共享代码:

import UIKit

struct MyColor {
  let title: String

  init(title: String) {
    self.title = title
  }
}

let red = MyColor(title: "Red")
let orange = MyColor(title: "Orange")
let yellow = MyColor(title: "Yellow")
let green = MyColor(title: "Green")
let blue = MyColor(title: "Blue")
let indigo = MyColor(title: "Indigo")
let violet = MyColor(title: "Violet")

let white = MyColor(title: "White")
let lightGray = MyColor(title: "Light Gray")
let darkGray = MyColor(title: "Dark Gray")
let black = MyColor(title: "Black")

// TODO: insert protocol conformance here!

print("Rainbow colors:")
for color in red...violet {
  print(color.title)
}

print("")
print("Grayscale colors:")
for color in white...black {
  print(color.title)
}

输出为:

Rainbow colors:
Red
Orange
Yellow
Green
Blue
Indigo
Violet

Grayscale colors:
White
Light Gray
Dark Gray
Black

我们可以通过遵守 ForwardIndexType 协议使 ... 工作:

let nullColor = MyColor(title: "Null")

extension MyColor: Equatable {}
@warn_unused_result
func ==(lhs: MyColor, rhs: MyColor) -> Bool {
  return lhs.title == rhs.title
}

extension MyColor: ForwardIndexType {
  @warn_unused_result
  func successor() -> MyColor {
    switch self {
    case red: return orange
    case orange: return yellow
    case yellow: return green
    case green: return blue
    case blue: return indigo
    case indigo: return violet
    case violet: return nullColor

    case white: return lightGray
    case lightGray: return darkGray
    case darkGray: return black
    case black: return nullColor

    case nullColor: fatalError()
    default: fatalError()
    }
  }
}

注意:即使您只从 red...violet 循环,也需要 nullColor(或某些终止符)。似乎系统实际上会执行 violet 情况,即使范围明显结束于 violet.

另请注意,这仅适用于结构、枚举或最终 classes。如果您尝试将上述结构更改为 class,您将收到以下错误:

execution failed: protocol 'ForwardIndexType' requirement '_failEarlyRangeCheck(_:bounds:)' cannot be satisfied by a non-final class ('MyColor') because it uses 'Self' in a non-parameter, non-result type positionmethod 'advancedBy' in non-final class 'MyColor' must return `Self` to conform to protocol 'ForwardIndexType'method 'advancedBy(_:limit:)' in non-final class 'MyColor' must return `Self` to conform to protocol 'ForwardIndexType'
error: method 'successor()' in non-final class 'MyColor' must return `Self` to conform to protocol '_Incrementable'
  func successor() -> MyColor {
       ^

有关为什么这不起作用的更多信息,请参阅

理论上你也应该能够通过实施 Comparable 协议来让它工作,但是当我尝试时,我得到:

error: binary operator '...' cannot be applied to two 'MyColor' operands
for color in red...violet {
             ~~~^  ~~~~~~
note: overloads for '...' exist with these partially matching parameter lists: (Bound, Bound), (Pos, Pos)
for color in red...violet {
                ^