Swift的Sequence 与 Collection

780 阅读4分钟

对于 Sequence 协议来说,表达的是既可以是一个有限的集合,也可以是一个无限的集合,而它只需要提供集合中的元素和如何访问这些元素的接口即可;对于 Collection 来说它是一个继承自 Sequence 的协议

image.png

1、Sequence

  • Sequence 是通过 Iterator 来访问元素的;Iterator 是一个迭代器
    let numbers = [2, 4, 6, 8] 
    for num in numbers { 
        print(num)
    }
    
    image.png
    • 当执行 for in 的时候,本质上会通过 Collection 创建一个迭代器,然后把当前的数组传给迭代器,最后调用迭代器的 next 方法将数组的元素遍历出来
    image.png
    • 我们来找到 Sequence.swift 这个文件,IteartorProtocol 是一个一次提供一个序列值的类型,它和 Sequence 协议是息息相关的, Sequence 每次通过创建迭代器来访问序列中的元素
    • 所以我们每次在使用 for..in 的时候,其实都是 使用这个集合的迭代器来遍历当前集合或者序列当中的元素

IteratorProtocol与Sequence

  • IteratorProtocol 中的内容:
    public protocol IteratorProtocol {
        /// The type of element traversed by the iterator.
        associatedtype Element
    
        mutating func next() -> Self.Element?
    }
    
  • Sequence 中的内容:
    public protocol Sequence {
    
        /// A type representing the sequence's elements.
        associatedtype Element where Self.Element == Self.Iterator.Element
    
        /// A type that provides the sequence's iteration interface and
        /// encapsulates its iteration state.
        associatedtype Iterator : IteratorProtocol
    
        /// Returns an iterator over the elements of this sequence.
        func makeIterator() -> Self.Iterator
    
        // other function
        // ......
    }
    

通过 Sequence 表达有限集合

  • 自定义的迭代器如下:
    struct LZSequence: Sequence {
        typealias Element = Int
    
        var arrCount: Int
    
        func makeIterator() -> LZIterator {
            return LZIterator(self)
        }
    }
    
    struct LZIterator: IteratorProtocol {
        typealias Element = Int
    
        let seq: LZSequence
    
        init(_ seq: LZSequence) {
            self.seq = seq
        }
    
        var count = 0
        mutating func next() -> Int? {
            guard count < seq.arrCount else {
                return nil
            }
            count += 1
            return count
        }
    }
    
    let seq = LZSequence(arrCount: 10)
    for element in seq {
        print(element)
    }
    
    //打印结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

2、Collection

  • Collection协议是建立在Sequence协议之上的,为有限的序列提供下标访问的能力,同时增加了count属性,自定义索引等特性
    • 是一个序列,其元素可以被多次遍历;通过定义 startIndex 和 endIndex 属性,表示集合起始和结束位置

2.1、环形数组

  • 普通数组来说,头尾的下标都是固定的,头下标为 0,尾下标为 length - 1。而环形数组(circular array)通常使用两个额外的数字去标识数组的头下标和尾下标

    struct RingBuffer<Element>{
        internal var _buffer: ContiguousArray<Element?>
        internal var headIndex: Int = 0
        internal var tailIndex: Int = 0
    
        internal var mask: Int{
            return self._buffer.count - 1
        }
    
        init(initalCapacity: Int) {
            let capcatiy = initalCapacity.nextPowerOf2()
    
            self._buffer = ContiguousArray<Element?>.init(repeating: nil, count:capcatiy)
        }
    
        mutating func advancedTailIndex(by: Int){
            self.tailIndex = self.indexAdvanced(index: self.tailIndex, by: by)
        }
    
        mutating func advancedHeadIndex(by: Int){
            self.headIndex = self.indexAdvanced(index: self.headIndex, by: by)
        }
    
        func indexAdvanced(index: Int, by: Int) -> Int{
            return (index + by) & self.mask
        }
    
        mutating func append(_ value: Element){
            _buffer[self.tailIndex] = value
            self.advancedTailIndex(by: 1)
    
            if self.tailIndex == self.headIndex {
                fatalError("out of bounds")
            }
        }
    
        mutating func read() -> Element?{
            let element = _buffer[self.headIndex]
            self.advancedHeadIndex(by: 1)
            return element
        }
    }
    

    这个结构体通过一个 ContiguousArray 类型的 _buffer 来记录环形数组的元素,ContiguousArray 可以理解为存粹的 Swift 的数组。和 OC 没有任何关系的数组,它的效率比 Array 更快。

    接下来我们通过 headIndex 和 tailIndex 来表示环形数组的起始和结束的位置。剩下的都是这个环形数组的一些初始化方法,移动环形数组的起始和结束的位置的方法,以及追加元素和读取元素的方法和获取元素索引的方法。

    我们看到初始化方法,nextPowerOf2 这个方法表示 2^n,这个表示使 capacity 始终为 2^n,其实现如下:

    extension FixedWidthInteger {
        /// Returns the next power of two.
        @inlinable
        func nextPowerOf2() -> Self {
            guard self != 0 else {
                return 1
            }
    
            return 1 << (Self.bitWidth - (self - 1).leadingZeroBitCount)
        }
    }
    

2.2、MutableCollection

  • MutableCollection 允许集合通过下标修改自身元素。根据这一点我们对 RingBuffer 进行一个 extension,代码如下:

    extension RingBuffer: Collection, MutableCollection {
        var startIndex: Int{
            return self.headIndex
        }
    
        var endIndex: Int{
            return self.tailIndex
        }
    
        subscript(position: Int) -> Element? {
            get{
                return self._buffer[position]
            }
            set{
                self._buffer[position] = newValue
            }
        }
    
        func index(after i: Int) -> Int {
            return (i + 1) & self.mask
        }
    }
    

    在这里 RingBuffer 遵循 Collection 协议,此时我们就需要实现 startIndex、endIndex 和 index(after:) 方法,index(after:) 是为了便于移动当前索引的位置。

    遵守 MutableCollection 协议是因为为了实现下标的 setter 方法,便于在语法上直接通过下标来访问并修改这个元素的值,例如数组:

    var nums = [1, 2, 3, 4]
    // 直接通过下标来修改元素的值
    nums[2] = 8
    

2.3、RangeReplaceableCollection

  • RangeReplaceableCollection 允许集合修改任意区间的元素
    extension RingBuffer: RangeReplaceableCollection {
        init() {
            self.init(initalCapacity: 10)
        }
    
        func remove(at position: Int) -> Element? {
            // ......
            return nil
        }
    }
    
    RangeReplaceableCollection 协议要求我们实现一个 init 方法,所以在这里就实现了一个默认的 init。接下来实现一个 remove(at:) 方法,对于环形数组来说,当我们删除一个元素的时候,首先把这个元素剔除,然后后面的元素往前移一个位置,然后再把尾节点指向当前空闲节点的位置。如果我们删除的位置,刚好是 headIndex 的位置,后面的元素也要往前移一个位置。那 remove(at:) 的实现如下:
    mutating func remove(at position: Int) -> Element? {
        let element = _buffer[position]
    
        _buffer[position] = nil
    
        switch position {
        case headIndex:
            advancedHeadIndex(by: 1)
        default:
            var currentIndex = position
            var nextIndex = indexAdvanced(index: position, by: 1)
    
            while nextIndex != tailIndex {
                _buffer.swapAt(currentIndex, nextIndex)
                currentIndex = nextIndex
                nextIndex = indexAdvanced(index: currentIndex, by: 1)
            }
    
            advancedTailIndex(by: -1)
        }
    
        return element
    }
    

2.4、BidirectionalCollection

  • BidirectionalCollection 可以向前或向后遍历集合。比如可以获取最后一个元素、反转序列、快速的获取倒序等

    既然正序是通过 subscript 和 index(after:) 来实现的,那么倒序添加一个 index(before:) 就可以往前递归了,这就好像双向链表 一样,只不过双向链表获取的是值,而这里的集合获取的都是索引,代码如下:

    extension RingBuffer: BidirectionalCollection{
        func index(before i: Int) -> Int {
            return (i - 1) & self.mask
        }
    }
    

2.5、RandomAccessCollection

  • RandomAccessCollection 可以任意访问集合元素
    extension RingBuffer: RandomAccessCollection{
        func index(_ i: Int, offsetBy distance: Int) -> Int {
            return (i + distance) & self.mask
        }
    
        func distance(from start: Int, to end: Int) -> Int {
            return end - start
        }
    }
    
    //测试代码
    var buffer: RingBuffer = RingBuffer<Int>.init(initalCapacity: 10)
    for i in 0..<10 {
        buffer.append(i)
    }
    
    print(buffer.index(0, offsetBy: 2)) // 2