11、Sequence协议与Collection

145 阅读4分钟

Sequence协议既可以表达一个有限的集合,也可以表达一个无限的集合,只需要提供集合中的元素和如何访问这些元素的接口即可。

Collection是一个继承自 Sequence 的协议。

一、Sequence协议

image.png

1、for in 的本质

Swift中的for in是一个语法糖。

let nums = [1, 2, 3, 4, 5];
for element in nums {
    print(element)
}

编译成sil文件看一下:

Snipaste_2022-10-09_10-54-48.png

可以看到,当执行 for in 的时候,本质上会通过 Collection 创建一个迭代器(Collection<>.makeIterator()),然后把当前的数组传给迭代器,最后调用迭代器next方法(IndexingIterator.next())将数组的元素遍历出来。

所以,我们也可以显试地调用迭代器

let nums = [1, 2, 3, 4, 5];

var numsIterator = nums.makeIterator()
for element in numsIterator {
    print(element)
}

var numsIterator2 = nums.makeIterator()
while let element2 = numsIterator2.next() {
    print(element2)
}

打开swift源码中 Collection.swift 文件,找到 IndexingIterator结构体 的定义

image.png

打开Sequence.swift这个文件,可以看到IteartorProtocol是一个提供一个序列值的类型,它和Sequence协议是息息相关的,Sequence协议每次通过创建迭代器来访问序列中的元素。

image.png

再来看Sequence协议的定义:

image.png

Sequence协议既可以表达一个有限的集合,也可以表达一个无限的集合,只需要提供集合中的元素,和如何访问这些元素的接口给它即可。

2、Sequence协议表达一个有限的集合:

struct CTIterator: IteratorProtocol{
    
    typealias Element = Int
    
    let sequence: CTSequence
    
    init(_ sequence: CTSequence) {
        self.sequence = sequence
    }
    
    var count = 0
    
    mutating func next() -> Int? {

        guard count < self.sequence.arrayCount else {
            return nil
        }
        
        count += 1
        
        return count;
    }
}


struct CTSequence:Sequence {
    
    typealias Element = Int
    
    var arrayCount: Int
    
    func makeIterator() -> CTIterator {
        return CTIterator(self)
    }
}

var ctIterator = CTSequence(arrayCount: 10)
for item in ctIterator{
    print(item)
}

3、Sequence协议表达一个无限的集合

struct unlimitedIterator:IteratorProtocol{
    
    typealias Element = Int
    
    let value:Int
    func next() -> Element? {
        return value
    }
}

var iterator = unlimitedIterator(value: 10)
while let x = iterator.next() {
    print(x)
}

二、Collection

1、环形数组

通过Collection来表达一个环型数组。

对于普通数组来说,头尾的下标都是固定的,头下标为 0,尾下标为 length - 1。

环形数组(circular array)通常使用两个额外的数字去标识数组的头下标和尾下标。

image.png

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 高)

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

初始化方法,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、Collection协议

  • 定义startIndexendIndex属性,表示集合起始和结束位置;
  • 实现一个index(after:)方法用于在集合中移动索引位置;
  • 定义一个只读的下标操作符。subscript(position: Int) -> Element? {get{}}
extension RingBuffer:Collection{
    
    var startIndex: Int{
        return self.headIndex
    }
    
    var endIndex: Int{
        return self.tailIndex
    }
    
    func index(after i: Int) -> Int {
        return (i + 1) & self.mask
    }
    
    subscript(position: Int) -> Element? {
        get{
            return self._buffer[position]
        }
    }
}

3、MutableCollection协议

允许集合通过下标修改自身元素,实现内容的与Collection协议内容基本一致,多了subscript(position: Int) -> Element? {}set()方法。

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

类似这样的操作,本质上都是实现了MutableCollection协议

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

4、RangeReplaceableCollection协议

RangeReplaceableCollection 允许集合修改任意区间的元素

RangeReplaceableCollection 协议要求我们实现一个 init 方法。

extension RingBuffer: RangeReplaceableCollection {
    init() {
        self.init(initalCapacity: 10)
    }
}

为了实现删除的功能,我们再实现一个remove()方法。

extension RingBuffer:RangeReplaceableCollection{
    
    init() {
        self.init(initalCapacity: 10)
    }
    
    mutating func remove(at i: Int) -> Element? {
 
        //要删除的索引
        var currentIndex = i
        //要删除的值
        let element = self._buffer[i]
        
        switch i {
            //要删除的索引 正好是 headIndex
        case self.headIndex:
            self.advancedHeadIndex(by: 1)    //把头结点向后移动一位
            self._buffer[currentIndex] = nil //把原来头结点的值置空
            //要删除的索引 不是 headIndex
        default:
            
            self._buffer[i] = nil //把对应节点值置空
            var nextIndex = self.indexAdvanced(index: i, by: 1) //拿到下一个节点索引
            
            //循环 直到碰到尾节点
            while nextIndex != self.tailIndex {
                
                self._buffer.swapAt(currentIndex, nextIndex) //要删除的值 跟 下一个节点的值 交换(相当于把要删除的值向后面推,当然 此刻这个值是nil)
                currentIndex = nextIndex //要删除的索引 向后移动一位
                nextIndex = self.indexAdvanced(index: currentIndex, by: 1) //下一个节点索引 向后移动一位
            }
            
            self.advancedTailIndex(by: -1) //将尾索引指向最后一个值
        }
        return element
    }
}

当然,RangeReplaceableCollection协议中还有许多方法,例如:replaceSubrange(_ :with:)removeFirst()removeFirst()等,依据需求来实现。

5. BidirectionalCollection协议

可以向前或向后遍历集合

可以获取最后一个元素、反转序列、快速的获取倒序等等。

Collection协议中正序是通过 index(after:) 来实现的,那么遵循了BidirectionalCollection协议后,倒序是通过实现 index(before:) ,这就好像双向链表 一样,只不过双向链表获取的是值,而这里的集合获取的都是索引。

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

6. RandomAccessCollection协议

访问任意集合元素

前面对集合元素的访问都是诸如:index=1,index=x,这样的访问,遵循了RandomAccessCollection协议之后,可以实现访问 距离初始索引x个距离的值。

extension RingBuffer: RandomAccessCollection{
    
    // i 是初始索引,distance是距离多少
    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

当然,对于这个集合,我们不去实现index(i: offsetBy:)也是可以的。因为我们把 index(after:)index(before:) 实现了,对于系统来说,它是可以通过这两个方法来推断当前要移动的距离。但是考虑到效率的问题,在这里直接实现会比系统去推断要好得多,因为直接省去了系统推断的时间。