swift - 实现 LazyCollectionProtocol

标签 swift

所以我正在学习有关 LazySequenceProtocol 和 LazyCollectionProtocol 的教程。

首先,我尝试了来自 Apple's API reference 的示例代码并且能够实现符合 LazySequenceProtocolLazyScanSequence,如下所示:

struct LazyScanIterator<Base : IteratorProtocol, ResultElement> : IteratorProtocol {
    mutating func next() -> ResultElement? {
        return nextElement.map { result in
            nextElement = base.next().map { nextPartialResult(result, $0) }
            return result
        }
    }
    private var nextElement: ResultElement? // The next result of next().
    private var base: Base                  // The underlying iterator.
    private let nextPartialResult: (ResultElement, Base.Element) -> ResultElement

    init(nextElement: ResultElement, base: Base, nextPartialResult: @escaping (ResultElement, Base.Element) -> ResultElement) {
        self.nextElement = nextElement
        self.base = base
        self.nextPartialResult = nextPartialResult
    }
}

struct LazyScanSequence<Base: Sequence, ResultElement> : LazySequenceProtocol // Chained operations on self are lazy, too
{
    func makeIterator() -> LazyScanIterator<Base.Iterator, ResultElement> {
        return LazyScanIterator(nextElement: initial, base: base.makeIterator(), nextPartialResult: nextPartialResult)
    }

    private let base: Base
    private let initial: ResultElement
    private let nextPartialResult: (ResultElement, Base.Iterator.Element) -> ResultElement

    init(initial: ResultElement, base: Base, nextPartialResult: @escaping (ResultElement, Base.Iterator.Element) -> ResultElement) {
        self.initial = initial
        self.base = base
        self.nextPartialResult = nextPartialResult
    }
}

成功了。当时我很开心。之后我想将它进一步扩展到 Collection,这样我就可以使用索引访问结果。

因为 LazyCollectionProtocol 是从 LazySequenceProtocolCollection 继承的,所以我不得不添加更多的东西:

struct LazyScanCollection<Base: Collection, ResultElement> : LazyCollectionProtocol {
    func makeIterator() -> LazyScanIterator<Base.Iterator, ResultElement> {
        return LazyScanIterator(nextElement: initial, base: base.makeIterator(), nextPartialResult: nextPartialResult)
    }

    private let base: Base
    private let initial: ResultElement
    private let nextPartialResult: (ResultElement, Base.Iterator.Element) -> ResultElement

    init(initial: ResultElement, base: Base, nextPartialResult: @escaping (ResultElement, Base.Iterator.Element) -> ResultElement) {
        self.initial = initial
        self.base = base
        self.nextPartialResult = nextPartialResult
    }

    // ^--- #1 conform to Sequence

    typealias Index = Base.Index
    var startIndex: Index { return base.startIndex }
    var endIndex: Index { return base.endIndex }
    subscript (position: Index) -> Base.Iterator.Element {
        precondition(indices.contains(position), "out of boundsss")
        return base[position]
    }
    func index(after i: Index) -> Index {
        return base.index(after: i)
    }

    // ^--- #2 conform to IndexableBase
}

在添加 #2 部分之前,我有 2 个错误说 this does not conform to protocol Collection & IndexableBase

添加#2后,即startIndexendIndexsubscriptindex(在:) 之后,有关IndexableBase 的错误之一消失了。但是我仍然有一个错误说这不符合Collection

我认为#2 将满足Collection 的所有要求,但我想我错了。我不确定如何解决这个问题。

最佳答案

问题是您的 LazyScanCollection 的推断类型存在冲突的 Iterator.Element – 你的 makeIterator()方法说它是 LazyScanIterator<Base.Iterator, ResultElement>.Element 类型(又名 ResultElement ),但是您的 subscript说它的类型是 Base.Iterator.Element .

正确的类型是ResultElement ,这是通过迭代每个基本集合的元素获得的,应用 nextPartialResult函数以获得下一个元素。但是,您的下标实现不会这样做——相反,它会尝试直接 下标基。这不是您想要的,因为它不会返回“已扫描”元素,它只会返回扫描前的元素。

因此,您需要实现一个下标,该下标返回集合中给定索引的“已扫描”元素。实现这一点的一种方法是定义您自己的索引类型,以便将基数的下一个索引与当前的“累积”元素一起存储到下标。例如:

// An underlying index for a LazyScanCollection. Note that this will be invalidated
// if the base is mutated.
struct _LazyScanCollectionIndex<BaseIndex : Comparable, ResultElement> : Comparable {

    var nextBaseIndex: BaseIndex // the next index of base to access.
    var element: ResultElement // the currently 'accumulated' element.

    static func ==(lhs: _LazyScanCollectionIndex, rhs: _LazyScanCollectionIndex) -> Bool {
        return lhs.nextBaseIndex == rhs.nextBaseIndex
    }

    static func <(lhs: _LazyScanCollectionIndex, rhs: _LazyScanCollectionIndex) -> Bool {
        return lhs.nextBaseIndex < rhs.nextBaseIndex
    }
}

然后您可以构建一个 enum包装器以定义结束索引(这也可以通过将 nextBaseIndex_LazyScanCollectionIndex 属性设为可选来实现,但我非常喜欢使用自定义枚举)。例如:

// A wrapper for the index of a LazyScanCollection
enum LazyScanCollectionIndex<BaseIndex : Comparable, ResultElement> : Comparable {

    // either an actual index, or the endIndex.
    case index(_LazyScanCollectionIndex<BaseIndex, ResultElement>)
    case endIndex

    static func ==(lhs: LazyScanCollectionIndex, rhs: LazyScanCollectionIndex) -> Bool {
        switch (lhs, rhs) {
        case (.endIndex, .endIndex):
            return true
        case let (.index(lhsIndex), .index(rhsIndex)):
            return lhsIndex == rhsIndex
        default:
            return false
        }
    }

    static func <(lhs: LazyScanCollectionIndex, rhs: LazyScanCollectionIndex) -> Bool {
        switch (lhs, rhs) {
        case (.index, .endIndex): // endIndex is greater than all indices.
            return true
        case let (.index(lhsIndex), .index(rhsIndex)):
            return lhsIndex < rhsIndex
        default:
            return false
        }
    }
}

然后你会想要调整你的 LazyScanCollection使用这个新索引类型的实现:

typealias Index = LazyScanCollectionIndex<Base.Index, ResultElement>

var startIndex: Index {
    return .index(_LazyScanCollectionIndex(nextBaseIndex: base.startIndex, element: initial))
}

var endIndex: Index {
    return .endIndex
}

func index(after i: Index) -> Index {

    guard case var .index(index) = i else {
        fatalError("Cannot advance past endIndex")
    }

    if index.nextBaseIndex < base.endIndex {
        // form the next partial result from the next index of the base and the
        // currently 'accumulated' element.
        index.element = nextPartialResult(index.element, base[index.nextBaseIndex])
        base.formIndex(after: &index.nextBaseIndex) // increment next index.
        return .index(index)
    } else {
        return .endIndex
    }
}

最后通过访问索引的 element 来实现你的下标属性:

subscript (position: Index) -> ResultElement {

    // ensure that the index is valid before subscripting.
    guard case let .index(index) = position, indices.contains(position) else {
        fatalError("Index \(position) out of bounds.")
    }

    return index.element
}

由于集合中元素的值取决于它之前所有元素的值,因此索引在线性时间内发生。如果您需要 O(1) 索引,您应该急切地执行 scan(_:_:)方法来生成数组而不是使用惰性集合。

关于swift - 实现 LazyCollectionProtocol,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41394314/

相关文章:

ios - 两个几乎相同的 UITapGestureRecognizers 之一的 Action 比另一个慢得多

ios - 如何自动关闭 SFSafariViewController?

ios - 滚动第二个 UITableView 与第二个 UITableView 一致

ios - 如何以正确的方式回到初始的 ViewController?

ios - Swift shapelayer 设置填充颜色不起作用

swift - libc++abi.dylib : terminating with uncaught exception of type NSException Alamofire

ios - 在 Swift 中为 NSDateComponenstFormatter 设置多个 allowedUnits

swift - 将 UIButtons 添加到折线图中的数据点,在 X 轴上绘制日期和时间

swift - 在 Swift 中重载方法时“参数标签不正确”

Swift:如何避免在所有继承类中重写这个 init()?