自定义迭代器以循环模式无限迭代集合

Vla*_*lad 7 collections iterator infinite-loop swift

我正在寻找迭代器以循环模式无限迭代集合.因此,当达到收集的结束索引时,迭代器应该在start索引处返回元素.

以下解决方案似乎有效,但我希望它可以更好地制作.

public struct LoopIterator<T: Collection>: IteratorProtocol {

   private let collection: T
   private var startIndexOffset: T.IndexDistance

   public init(collection: T) {
      self.collection = collection
      startIndexOffset = 0
   }

   public mutating func next() -> T.Iterator.Element? {
      guard !collection.isEmpty else {
         return nil
      }
      let index = collection.index(collection.startIndex, offsetBy: startIndexOffset)
      startIndexOffset += T.IndexDistance(1)
      if startIndexOffset >= collection.count {
         startIndexOffset = 0
      }
      return collection[index]
   }
}

extension Array {
   func makeLoopIterator() -> LoopIterator<Array> {
      return LoopIterator(collection: self)
   }
}

// Testing...
// Will print: 1, 2, 3, 1, 2, 3
var it = [1, 2, 3].makeLoopIterator()
for _ in 0..<6 {
   print(it.next())
}
Run Code Online (Sandbox Code Playgroud)

这是做自定义迭代器的正确方法吗?有什么可以改进的?

谢谢!

Rob*_*ier 11

在Swift 3(您正在使用)中,索引旨在由集合本身进行处理.有了它,您可以按如下方式简化:

public struct LoopIterator<Base: Collection>: IteratorProtocol {

    private let collection: Base
    private var index: Base.Index

    public init(collection: Base) {
        self.collection = collection
        self.index = collection.startIndex
    }

    public mutating func next() -> Base.Iterator.Element? {
        guard !collection.isEmpty else {
            return nil
        }

        let result = collection[index]
        collection.formIndex(after: &index) // (*) See discussion below 
        if index == collection.endIndex {
            index = collection.startIndex
        }
        return result
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们只需向前移动索引,如果它现在指向结尾,则将其重置为开头.不需要countIndexDistance.

请注意,我在formIndex这里使用过,AnyIndex因为你的Iterator可以处理任何Collection(因此也适用于任何索引),因此可以在某些模糊的情况下(特别是在周围)提高性能.更简单的版本将是index = collection.index(after: index),并且在大多数情况下可能更好.

有关Swift 3指数的所有细节,请参阅SE-0065.


Ima*_*tit 8

使用 Swift 5,您可以使用以下示例之一来解决您的问题。


#1. 使用AnyIterator

作为创建符合 的新类型的替代方法IteratorProtocol,您可以使用AnyIterator. 以下代码基于 Rob Napier 的回答,展示了如何使用它:

extension Array {

    func makeInfiniteLoopIterator() -> AnyIterator<Element> {
        var index = self.startIndex

        return AnyIterator({
            if self.isEmpty {
                return nil
            }

            let result = self[index]

            index = self.index(after: index)
            if index == self.endIndex {
                index = self.startIndex
            }

            return result
        })
    }

}
Run Code Online (Sandbox Code Playgroud)

用法:

let infiniteLoopIterator = [1, 2, 3].makeInfiniteLoopIterator()
for val in infiniteLoopIterator.prefix(5) {
    print(val)
}

/*
 prints:
 1
 2
 3
 1
 2
 */
Run Code Online (Sandbox Code Playgroud)

let infiniteLoopIterator = [1, 2, 3].makeInfiniteLoopIterator()
let array = Array(infiniteLoopIterator.prefix(7))
print(array) // prints: [1, 2, 3, 1, 2, 3, 1]
Run Code Online (Sandbox Code Playgroud)
let infiniteLoopIterator = [1, 2, 3].makeInfiniteLoopIterator()

let val1 = infiniteLoopIterator.next()
let val2 = infiniteLoopIterator.next()
let val3 = infiniteLoopIterator.next()
let val4 = infiniteLoopIterator.next()

print(String(describing: val1)) // prints: Optional(1)
print(String(describing: val2)) // prints: Optional(2)
print(String(describing: val3)) // prints: Optional(3)
print(String(describing: val4)) // prints: Optional(1)
Run Code Online (Sandbox Code Playgroud)

#2. 使用AnySequence

类似的方法是使用AnySequence

extension Array {

    func makeInfiniteSequence() -> AnySequence<Element> {
        return AnySequence({ () -> AnyIterator<Element> in
            var index = self.startIndex

            return AnyIterator({
                if self.isEmpty {
                    return nil
                }

                let result = self[index]

                self.formIndex(after: &index) // alternative to: index = self.index(after: index)
                if index == self.endIndex {
                    index = self.startIndex
                }

                return result
            })
        })
    }

}
Run Code Online (Sandbox Code Playgroud)

用法:

let infiniteSequence = [1, 2, 3].makeInfiniteSequence()
for val in infiniteSequence.prefix(5) {
    print(val)
}

/*
 prints:
 1
 2
 3
 1
 2
 */
Run Code Online (Sandbox Code Playgroud)
let infiniteSequence = [1, 2, 3].makeInfiniteSequence()
let array = Array(infiniteSequence.prefix(7))
print(array) // prints: [1, 2, 3, 1, 2, 3, 1]
Run Code Online (Sandbox Code Playgroud)