Swift 2.2:非+1增加迭代器循环

时间:2016-03-25 09:36:32

标签: arrays swift for-loop iterator

我想知道关于Swift 2.2中非+1增加迭代器循环的最佳解决方案,因为旧样式现已弃用:

for (var i = 0; i < planets.count; i += 1) {
    let string = planets[i]
    if (string == "Venus" || string == "Saturn") {
        i += 2
    }
}

我的第一个解决方案是使用有效的while

var i = 0
while i < planets.count {
    let string = planets[i]
    if (string == "Venus" || string == "Saturn") {
        i += 2
    }
    i += 1
}

但在整个代码中多次使用它并不是很方便。所以我创建了一个似乎也可以工作的数组扩展:

extension Array {
    func loopIncrement(callback: (inout i: Int) -> ()) {
        var index: Int = 0
        while index < self.count {
            callback(i: &index)
            index += 1
        }
    }
    func loopDecrement(callback: (inout i: Int) -> ()) {
        var index: Int = self.count - 1
        while index >= 0 {
            callback(i: &index)
            index -= 1
        }
    }
}

像这样使用它:

planets.loopIncrement { (i) in
    let string = planets[i]
    if (string == "Venus" || string == "Saturn") {
        i += 2
    }
}

planets.loopDecrement { (i) in
    let string = planets[i]
    if (string == "Venus" || string == "Saturn") {
        i -= 2
    }
}

现在这很好,但我确信有更好的方法可以解决这个问题。

所以我们在这里开始讨论。非常感谢任何帮助!

如果你想看一下,这是Playground文件:NonPlusOneIncrementLoops.playground

示例:

在下面的示例中,您可以看到需要更改循环索引的位置。 有一个printObject数组,其中包含PrintObject个类,用于定义应将printText打印到调试区域的次数。 printCount属性是随机生成的,因此在循环之前不知道。

class PrintObject {
    var printCount: Int = 0
    var printText: String = "<Missing>"
}

var printObjects = [PrintObject]()

for i in 0...6 {
    let randomCount = Int(arc4random_uniform(2)) + 1
    let printObject = PrintObject()
    printObject.printCount = randomCount
    printObject.printText = "This is line \(i)"
    printObjects.append(printObject)
}

printObjects.loopIncrement { (i) in
    let printObject = printObjects[i]
    printObject.printCount -= 1
    if printObject.printCount > 0 {
        i -= 1
    }
    print(printObject.printText)
}

这显然是一个微不足道的例子,但它可能会使某些情况更清楚,在这种情况下,更改循环索引是有意义的。

5 个答案:

答案 0 :(得分:0)

您可以实现Generator / SequenceType以便能够使用for-in-loop:

struct IrregularGenerator<T> : GeneratorType, SequenceType {
    let getIncrement: T -> Int
    var index: Int
    let array: [T]

    init(array: [T], start: Int, getIncrement: T -> Int) {
        guard start >= 0 && start < array.count else {
            preconditionFailure("start index is out of bounds")
        }
        self.array = array
        self.index = start
        self.getIncrement = getIncrement
    }

    mutating func next() -> T? {
        guard index >= 0 && index < array.count else {
            return nil
        }
        defer {
            index += getIncrement(array[index])
        }
        return array[index]
    }
}

let planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]

for planet in IrregularGenerator(array: planets, start: planets.count - 1, getIncrement: { ($0 == "Venus" || $0 == "Saturn") ? -3 : -1 }) {
    print(planet)
}

这是用我的IrregularGenerator重写的PrintObject示例(我改为删除0增量保护,请注意,这可能会引发无限循环):

for printObject in IrregularGenerator(
    array: printObjects, start: 0,
    getIncrement: { $0.printCount -= 1; return $0.printCount == 0 ? 1 : 0 }
) {
    print(printObject.printText)
}

答案 1 :(得分:0)

如果您只想要每个第二个元素

evens = Array(1...10).filter { $0 % 2 == 0 }

许多详细的代码

func isEven(number: Int) -> Bool {
  return number % 2 == 0
}

evens = Array(1...10).filter(isEven)

答案 2 :(得分:0)

此外,如果您事先知道序列 ,则Swift的 Stridable 类型(即 Int )具有stride(to:by:)stride(through:by:)方法。像这样使用它:

for i in 0.stride(to: 10, by: 2) {
  // ...
}

i将采用02468的值。

Strideable Protocol ReferenceSwiftDoc中的更多内容。

答案 3 :(得分:0)

我的方法是编写一个专门用于“行星”的全局函数:

func planetIncrement(planet: String) -> Int {
    return planet == "Venus" || planet == "Saturn" ? 2 : 1
}

然后只需使用while循环中的函数:

var i = 0
while i < planets.count {
    i += planetIncrement(planets[i])
}

好处是函数和while循环都很简单易读。折磨和复杂的循环逻辑是错误的一个配方,所以我会保持简单。

你写的loopIncrementloopDecrement函数非常酷,但它们可能有点过分,并且要求你基本上为每个不同的用例编写一个完整的函数。

另一种可以重写while循环的方法是使用递归函数:

func iteratePlanets(planets: [String], index i: Int) {
    switch i {
    case let i where i >= planets.count:
        return
    case let i where planets[i] == "Venus" || planets[i] == "Saturn"
        iteratePlanets(planets, index: i + 2)
    case let i:
        iteratePlanets(planets, index: i + 1)
    }
}

这与while循环完全相同。

编辑:

也许这样的事情会起作用:

extension Array {

    func loop(start: Int = 0, condition: (Int -> Bool)? = nil, increment: Element -> Int = { _ in 1 }, action: (Element -> ())? = nil) {
        var index = start
        while condition?(index) ?? (index < self.count) {
            action?(self[index])
            index += increment(self[index])
        }
    }
}

它循环,并允许您提供自定义条件来终止循环,更改增量,甚至在循环内执行操作,如下所示:

planets.loop(increment: { planet in
    return planet == "Venus" || planet == "Saturn" ? 2 : 1
})
但是,我自己的感觉是这会掩盖代码。由于每个用例都有自己的内部循环逻辑,因此每次都必须重新编写逻辑。这意味着你每次都要写一个新函数。所以你不妨在循环中编写逻辑,或编写一个简单易懂的外部函数,以便一切都清晰。

也可以将此概括为所有CollectionType s:

extension CollectionType {

    func loop(start start: Index.Distance = 0, condition: (Index -> Bool)? = nil, increment: Generator.Element -> Index.Distance = { _ in 1 }, action: (Generator.Element -> ())? = nil) {
        var index = self.startIndex.advancedBy(start)
        while condition?(index) ?? (index.distanceTo(self.endIndex) > 0) {
            let item = self[index]
            action?(item)
            index = index.advancedBy(increment(item))
        }
    }
}

例如,这会迭代Set的所有元素,打印每个项目......

Set([1, 2, 3, 4, 5]).loop(action: { print($0) })

...这会以相反的顺序迭代一个数组,打印每个项目:

[1, 2, 3, 4, 5].loop(start: 4, condition: { $0 >= 0 }, increment: { _ in -1}, action: { print($0) })

它实际上很好地反映了C风格的循环。填写所有参数后,函数调用如下所示:

planets.loop(
    start: 0,
    condition: { $0 < planets.count },
    increment: { $0 == "Venus" || $0 == "Saturn" ? 2 : 1 },
    action: { print($0) })

使用您的printObjects示例,您可以像这样使用它:

printObjects.loop(
    increment: { $0.printCount >= 0 ? 0 : 1 },
    action: { 
        $0.printText = "This is line \($0.printCount)"
        print($0.printText)
        $0.printCount -= 1 })

编辑:

我将函数参数名称从terminator更改为condition,以便在condition返回true时更清楚循环应该继续,与编写C风格的循环更为一致。

答案 4 :(得分:0)

我对此问题仍然有点不清楚,但这里有......

我认为实际问题看起来像这样:

let planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter"]
for (var i = 0; i < planets.count; i += 1) {
    let string = planets[i]
    if (string == "Venus" || string == "Saturn") {
        i += 2
    }
    print(string)
}

这会产生:

Mercury
Venus
Jupiter

如果这是正确的,那么问题归结为:当我们在行星中循环时,我们最近看到金星(或土星)?如果我们最近看到它,请跳过此迭代。 Swift for&#34;跳过这个迭代&#34;是continue。所以我们要做的就是跟踪我们最近看到金星的情况:

var marker = -100
for (ix,planet) in planets.enumerate() {
    if ix - marker < 3 {
        continue
    }
    if (planet == "Venus" || planet == "Saturn") {
        marker = ix
    }
    print(planet)
}

话虽如此,我并不认为我会如何写这个。我要做的是缩短行星本身的清单。现在,您可以对剩余行星列表执行任何操作。所以现在我们的目标是从行星列表中删除跟随金星(或土星)的行星以及随后的行星:

let fewerPlanets = planets.enumerate().filter {
    ix, planet in
    for ix in [ix-1, ix-2] {
        if ix >= 0 && (planets[ix] == "Venus" || planets[ix] == "Saturn") {
            return false
        }
    }
    return true
}.map {$1}

现在fewerPlanets["Mercury", "Venus", "Jupiter"],我们可以循环 并做我们想做的任何事情。