Swfit进阶-21-Swift中的集合

152 阅读5分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

  • 本文主要介绍swift中集合。

1. Sequence

Sequence 协议来说,表达的是既可以是一个有限的集合,也可以是一个无限的集合,而它只需 要提供集合中的元素,和如何访问这些元素的接口即可。看下它们的关系

image.png 可以发现Sequence是继承Collection的,Collection有一些子类。对于Sequence来说是通过迭代器Iterator进行访问集合的元素的。

1.1 Iterator

我们看下下面的for循环代码


let numbers = [2, 4, 6, 8]

for num in numbers{

    

    print(num)

}

我们编译成SIL文件

image.png

对于这个for循环是创建一个迭代器,不断的通过next迭代数组中的元素。迭代器是遵循的迭代器协议IteratorProtocol

image.png

可以看到关联类型是一个泛型,里面异变方法不断查找下一个。对于Sequence来说,它也是一个协议。

image.png

可以发现指定迭代器要遵守迭代器协议同时里面的元素类型要相同,之后创建一个迭代器。

1.2 自定义Sequence

我们来自定义的试一下:假设我们要用一个结构体来模拟一个集合,对于一个给定的初始值,那 么当前集合中包含从 0...count 的整形集合。

struct KBIteratorProtocol:IteratorProtocol

{

    typealias Element = Int

    

    let sequence :KBSequence

    

    init(_ sequence:KBSequence) {

        

        self.sequence = sequence

        

    }

    func next() -> Int? {

        

        var count = 0

        guard count < self.sequence.arrCount  else {

            

            return nil

        }

        count += 1

        

        return count

    }

    

}



struct KBSequence:Sequence {

    

    typealias Element = Int

    

    var arrCount:Int

    

    func makeIterator() -> KBIteratorProtocol {

        

        

        return KBIteratorProtocol(self)

    }

    

    

}

var sequnce = KBSequence(arrCount: 10)

for element in sequnce{

    

    print(element)

}

可以发现进入了死循环,count每次进来都是0

image.png

修改下

image.png

可以发现 SequenceIterator的关系,集合Sequence生成Iterator,Iterator调用next生成element。

1.3 无限集合

我们想要表达一个无限集合呢?我们直接使用迭代器

struct unlimitedIterator: IteratorProtocol{

    

    let value: Int

    func next() -> Int? {

        

     return value

        

    }

}

var iterator = unlimitedIterator(value: 10)

while let x = iterator.next(){

    

   print(x)

    

}

只是表达,没有什么实际的意义。

2. collecton

定义startIndexendIndex属性,表示集合起始和结束位置; 定义一个只读的下标操作符; 实现一个index(after:)方法用于在集合中移动索引位置;

2.1 环形数组

对于环形数组顾名思义:首位相连,通常使用两个额外的数字去标识数组的头下标和尾下标。而普通的数组则头尾的下标都是固定的,头下标为 0,尾下标为 length - 1

我们定义一个环形数组

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)

    }

}



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

2.2 MutableCollection

我们看下MutableCollection的方法实现

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

    }

}

可以根据位置进行修改我们数组的的值

image.png

我们的数组遵循了MutableCollection协议,因此可以根据下标修改数组中的元素。

2.3 RangeReplaceableCollection

这个协议允许我们通过一个集合来替换当前集合当中任意自己的元素,同时支持我们删除和插入 元素的操作。

extension RingBuffer: RandomAccessCollection{

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
}

      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

      }

}

我们要提供一个默认初始化的方法。 在remove方法中,我们要判断对于当前range范围的位置,对于一些特殊的点,我们要进行判断。 对于环形数组来说,当我们删除一个元素的时候,首先把这个元素剔除,然后后面的元素往前移一个位置,然后再把尾节点指向当前空闲节点的位置。如果我们删除的位置,刚好是 headIndex 的位置,后面的元素也要往前移一个位置。

2.4 BidirectionalCollection

extension RingBuffer: BidirectionalCollection{

    func index(before i: Int) -> Int {

        return (i - 1) & self.mask

    }

}

可以向前向后遍历集合,对于之前我们通过next,进行不断查找下一个。使用before表示向前进行遍历,在MutableCollection中我们已经实现了向后的遍历after,因此不用在实现了。

2.5 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

      }

}

对于我们之前访问都是通过下标进行访问,通过实现index(_:offsetBy:)distance(from:to:) 我们任意访问集合元素。