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

问题描述 投票:5回答:2

我正在寻找迭代器以循环模式无限迭代集合。因此,当达到收集的结束索引时,迭代器应该在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())
}

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

谢谢!

swift collections iterator infinite-loop
2个回答
10
投票

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

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

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

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


1
投票

使用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]

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

            return result
        })
    }

}

用法:

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

/*
 prints:
 1
 2
 3
 1
 2
 */

let infiniteLoopIterator = [1, 2, 3].makeInfiniteLoopIterator()
let array = Array(infiniteLoopIterator.prefix(7))
print(array) // prints: [1, 2, 3, 1, 2, 3, 1]
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)

#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)
                if index == self.endIndex {
                    index = self.startIndex
                }

                return result
            })
        })
    }

}

用法:

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

/*
 prints:
 1
 2
 3
 1
 2
 */
let infiniteSequence = [1, 2, 3].makeInfiniteSequence()
let array = Array(infiniteSequence.prefix(7))
print(array) // prints: [1, 2, 3, 1, 2, 3, 1]
© www.soinside.com 2019 - 2024. All rights reserved.