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

时间:2016-08-11 23:57:43

标签: swift range for-in-loop

在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 { ... }

3 个答案:

答案 0 :(得分:2)

(请注意,这是Swift 3.如果你想要Swift 2.2,请参阅Senseful的答案,这与旧协议基本相同。)

您希望Color Strideable Stride Int。然后您可以免费获得.....<。我们这样做。

首先,我要假设你的类型是这样的。这只是一个例子。只要您可以实现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
    }
}

现在它可能等同,我们应该想出“秩序”的意思。这是一种方式。由于结构可能会产生意想不到的值,我已经说过这些都是平等的并且在我的彩虹颜色之前排序。如果这是一个枚举,我们就不必担心这种情况。

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
    }
}

并且ta da,你的...

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

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

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
    }
}

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

答案 1 :(得分:2)

(注意:这适用于Swift 2.2。如果您需要Swift 3版本,请参阅Rob's answer。)

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协议,因为它返回一个Interval,我们需要一个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()
    }
  }
}

注意:即使您只从nullColor循环,也需要red...violet(或某些终结符)。即使范围明确地在violet结束,系统似乎也会实际执行violet案例。

另请注意,这仅适用于struct,enums或final类。如果您尝试将上述结构更改为类,则会收到以下错误:

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 {
       ^

有关为何无法运作的详情,请参阅Rob's answer here

理论上你也应该通过实现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 {
                ^

答案 2 :(得分:1)

所以......你可以对任何类型进行for - in循环。您所要做的就是实现符合SequenceType的内容。然后,一旦您实现了该类或结构,就像创建一个返回...的{​​{1}}运算符一样简单。

假设如下:

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 } } } 后,RainbowColorSequence功能非常简单:

...

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

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

有关生成器逻辑可能的示例,请查看this question。我可以很容易地将for color in red...violet { print(color.name) } 运算符添加到该链接后面的代码中,因为其余逻辑已经存在,...实际上只是...初始化的轻量级包装器。 }。

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