草庐IT

Swift探索( 十): Sequence && Collection

Lee_Toto 2023-03-28 原文

一:Sequence

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

Sequence和Collection的关系.png

1.1 迭代器 Iterator

Sequence 是通过迭代器 Iterator 来访问元素的,那么什么是迭代器?直接来看 for..in 函数

let numbers = [1, 2, 3, 4]

for num in numbers {
    print(num)
}

for..in 函数其实是一种语法糖,他的本质是怎么去调用的呢?编译成 SIL 并定位到 main 函数中 for..in 的调用不重要的代码我就直接省略了

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  ...
  // function_ref Collection<>.makeIterator()
  %36 = function_ref @$sSlss16IndexingIteratorVyxG0B0RtzrlE04makeB0ACyF : $@convention(method) <τ_0_0 where τ_0_0 : Collection, τ_0_0.Iterator == IndexingIterator<τ_0_0>> (@in τ_0_0) -> @out IndexingIterator<τ_0_0> // user: %37
  %37 = apply %36<Array<Int>>(%31, %34) : $@convention(method) <τ_0_0 where τ_0_0 : Collection, τ_0_0.Iterator == IndexingIterator<τ_0_0>> (@in τ_0_0) -> @out IndexingIterator<τ_0_0>
  %38 = tuple ()
  dealloc_stack %34 : $*Array<Int>                // id: %39
  br bb1                                          // id: %40

bb1:                                              // Preds: bb3 bb0
  %41 = alloc_stack $Optional<Int>                // users: %47, %44, %48
  %42 = begin_access [modify] [static] %31 : $*IndexingIterator<Array<Int>> // users: %44, %46
  // function_ref IndexingIterator.next()
  %43 = function_ref @$ss16IndexingIteratorV4next7ElementQzSgyF : $@convention(method) <τ_0_0 where τ_0_0 : Collection> (@inout IndexingIterator<τ_0_0>) -> @out Optional<τ_0_0.Element> // user: %44
  %44 = apply %43<Array<Int>>(%41, %42) : $@convention(method) <τ_0_0 where τ_0_0 : Collection> (@inout IndexingIterator<τ_0_0>) -> @out Optional<τ_0_0.Element>
  %45 = tuple ()
  end_access %42 : $*IndexingIterator<Array<Int>> // id: %46
  ...
} // end sil function 'main'
  • // function_ref Collection<>.makeIterator()
    %36 = function_ref @$sSlss16IndexingIteratorVyxG0B0RtzrlE04makeB0ACyF : $@convention(method) <τ_0_0 where τ_0_0 : Collection, τ_0_0.Iterator == IndexingIterator<τ_0_0>> (@in τ_0_0) -> @out IndexingIterator<τ_0_0> // user: %37
    这里我们可以看到调用了一个 makeIterator() 的方法,这个方法需要两个参数一个是在上文中创建 的 IndexingIterator , 一个是 Array 的引用。
  • // function_ref IndexingIterator.next()
    %43 = function_ref @$ss16IndexingIteratorV4next7ElementQzSgyF : $@convention(method) <τ_0_0 where τ_0_0 : Collection> (@inout IndexingIterator<τ_0_0>) -> @out Optional<τ_0_0.Element> // user: %44
    这里调用 IndexingIteratornext() 方法来遍历数组中一个又一个的元素。
    因此可以发现执行 for..in 的时候,本质上会通过 Collection 创建一个迭代器 Iterator,然后把当前的数组传给迭代器 Iterator,最后调用迭代器 Iteratornext 方法将数组的元素遍历出来。

接着我们打开 Swift源码 定位到 collection.swift 中的 IndexingIterator

@frozen
public struct IndexingIterator<Elements: Collection> {
  @usableFromInline
  internal let _elements: Elements
  @usableFromInline
  internal var _position: Elements.Index

  /// Creates an iterator over the given collection.
  @inlinable
  @inline(__always)
  public /// @testable
  init(_elements: Elements) {
    self._elements = _elements
    self._position = _elements.startIndex
  }

  /// Creates an iterator over the given collection.
  @inlinable
  @inline(__always)
  public /// @testable
  init(_elements: Elements, _position: Elements.Index) {
    self._elements = _elements
    self._position = _position
  }
}

extension IndexingIterator: IteratorProtocol, Sequence {
  public typealias Element = Elements.Element
  public typealias Iterator = IndexingIterator<Elements>
  public typealias SubSequence = AnySequence<Element>

  @inlinable
  @inline(__always)
  public mutating func next() -> Elements.Element? {
    if _position == _elements.endIndex { return nil }
    let element = _elements[_position]
    _elements.formIndex(after: &_position)
    return element
  }
}

可以看见 IndexingIterator 是一个接收泛型参数 Collection 的结构体,并且这个结构体遵循了两个协议,分别是 IteratorProtocolSequenceIteratorProtocol 是一个一次提供一个序列值的类型, 它和 Sequence 协议是息息相关的, 每次通过创建迭代器来访问序列中的元素。
所以我们每次在使用 for..in 的时候,其实都是使用这个集合的迭代器来遍历当前集合或者序列当中的元素。

1.2 IteratorProtocol协议

sequence.swift 文件中定位到 IteratorProtocol

public protocol IteratorProtocol {
  /// The type of element traversed by the iterator.
  associatedtype Element

  mutating func next() -> Element?
}

可以看到有一个关联类型,实现该协议时需要执行 Element 的类型,还有一个 next() 函数返回的是这个关联类型。

1.3 Sequence协议

接着在 sequence.swift 文件中 定位到 Sequence

public protocol Sequence {
  /// A type representing the sequence's elements.
  associatedtype Element

  /// A type that provides the sequence's iteration interface and
  /// encapsulates its iteration state.
  associatedtype Iterator: IteratorProtocol where Iterator.Element == Element

  /// A type that represents a subsequence of some of the sequence's elements.
  // associatedtype SubSequence: Sequence = AnySequence<Element>
  //   where Element == SubSequence.Element,
  //         SubSequence.SubSequence == SubSequence
  // typealias SubSequence = AnySequence<Element>

  /// Returns an iterator over the elements of this sequence.
  __consuming func makeIterator() -> Iterator

  ...
}
  • 可以看到这里也是有一个关联类型 Element
  • 还有一个关联类型 Iterator 并且 Iterator 要遵循 IteratorProtocol 协议并且 Iterator 对于协议 IteratorProtocol 的关联类型要与 Sequence 的关联类型相等
  • 有一个创建迭代器的方法 makeIterator() 返回当前关联类型 Iterator (这个就类似与车间一样,Iterator 就是一条流水线)
    因此对于 Sequence 协议来说,表达的是既可以是一个有限的集合,也可以是一个无限的集合,而它只需要提供集合中的元素,和如何访问这些元素的接口即可。

1.4 通过Sequence协议自定义有限的集合

直接上代码

struct MyIterator: IteratorProtocol {
    typealias Element = Int
    
    let seq: MySequence
    
    init(_ seq: MySequence) {
        self.seq = seq
    }
    
    var count = 0
    
    // 对于迭代器来说要遍历当前 `sequence` 中的元素
    mutating func next() -> Int? {
        guard count < self.seq.arrayCount else {
            return nil
        }
        count += 1
        return count
    }
}

struct MySequence: Sequence {
    typealias Element = Int
    
    var arrayCount: Int
    
    // 对于 sequence 来说需要一个迭代器
    func makeIterator() -> MyIterator {
        return MyIterator.init(self)
    }
}


var seq = MySequence(arrayCount: 10)

for element in seq {
    print(element)
}

打印结果:
1
2
3
4
5
6
7
8
9
10

对于 sequence 来说需要一个迭代器,而对于对于迭代器来说要遍历当前 sequence 中的元素,所以需要 MyIterator 提供一个遍历的构造函数 init(_ seq: MySequence)
同样的我们也可以创建一个无限的序列,但是在实际开发当中一般不会出现这种场景,所以这里就不继续了。

二:Collection

2.1 环形数组

环形数组是一种用于表示一个头尾相连的缓冲区的数据结构。跟环形队列差不多。接下来通过 Collection 来表达一个环形数组。

// 参照源码
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>{
    
    //ContiguousArray 可以理解为存粹的 Swift 的数组。和 OC 没有任何关系的数组,它的效率比 Array 更快。
    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

2.2 MutableCollection

MutableCollection 允许集合通过下标修改自身元素。对于 MutableCollection 需要

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

这里实现了 subscriptgetset ,对于 Collection 来说可以不提供 set ,这个时候我们就没有办法通过下标的方式改变自身元素了,所以对 MutableColletion 来说下标语法提供一个 setter 就行。就好比

var array = [1, 2, 3, 4]
array[1] = 5

这里直接通过下标修改元素

2.3 RangeReplaceableCollection

RangeReplaceableCollection 允许集合修改任意区间的元素。对于 RangeReplaceableCollection 我们需要提供一个默认的 init 方法。其他的如果不需要 RangeReplaceableCollection 都有默认的实现。

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

    mutating func remove(at i: Int) -> Element? {
        var currentIndex = i
        let element = _buffer[i]
        self._buffer[currentIndex] = nil
        
        switch i {
        case self.headIndex:
            self.advancedHeadIndex(by: 1)
        default:
            var nextIndex = self.indexAdvanced(index: i, by: 1)
            while nextIndex != self.tailIndex {
                self._buffer.swapAt(currentIndex, nextIndex)
                currentIndex = nextIndex
                nextIndex = self.indexAdvanced(index: currentIndex, by: 1)
            }
        }
        return element
    }
}

对于环形数组来说这里的 remove 方法:首先都是把当前位置的元素删除,然后根据删除的位置来进行判断

  • 当删除的位置刚好是 headIndex 的位置,那么就将 headIndex 往后移动一个位置。
  • 不是 headIndex 的位置,就需要将后面的元素往前移一个位置,然后再把尾节点指向当前空闲节点的位置。

2.4 BidirectionalCollection

BidirectionalCollection 可以向前或向后遍历集合。 比如可以获取最后一个元素、反转序列、快速的获取倒序等等。既然正序是通过 subscriptindex(after:) 来实现的,那么倒序添加一个 index(before:) 就可以往前递归了,这就好像双向链表 Remove 一样,只不过双向链表获取的是值,而这里的集合获取的都是索引。

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

2.5 RandomAccessCollection

RandomAccessCollection 任意访问集合元素。对于 RandomAccessCollection 需要实现 index(_:offsetBy:)distance(from:to:)

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

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

使用示例代码

var buffer: RingBuffer = RingBuffer<Int>.init(initalCapacity: 10)
for i in 0..<10 {
    buffer.append(i)
}

print(buffer.index(0, offsetBy: 5))

打印结果:
5

有关Swift探索( 十): Sequence && Collection的更多相关文章

  1. ruby-on-rails - rails 上的 ruby : radio buttons for collection select - 2

    我有一个集合选择:此方法的单选按钮是什么?谢谢 最佳答案 Rails3中没有这样的助手。在Rails4中,它是collection_radio_buttons. 关于ruby-on-rails-rails上的ruby:radiobuttonsforcollectionselect,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/18525986/

  2. ruby-on-rails - RVM、Ruby 1.9.2、Rails 2.3.8、Passenger 和 "invalid byte sequence in US-ASCII" - 2

    我刚刚开始从Ruby1.8.7升级到Ruby1.9.2(使用RVM)。我的所有应用程序都使用“脚本/服务器”(或“rails服务器”)和1.9.2运行,但是,只有Rails3.0.0RC应用程序可以与Passenger一起使用。Rails2.3.8应用给出的错误信息是:invalidbytesequenceinUS-ASCII我猜这是一个Passenger问题。我使用找到的RVM指南安装了Passenger2.2.15here.任何想法如何修复这个错误?谢谢。我已更新以包含堆栈跟踪:/Users/kevin/.rvm/gems/ruby-1.9.2-p0/gems/actionpack

  3. ruby-on-rails - Rails 导入 CSV 错误 : invalid byte sequence in UTF-8 - 2

    尝试在我的Rails应用程序中导入CSV文件时,出现错误UTF-8中的无效字节序列。一切正常,直到我添加了一个gsub方法来将其中一个CSV列与我的数据库中的一个字段进行比较。当我导入CSV文件时,我想检查每一行的地址是否包含在特定客户端的不同地址数组中。我有一个带有alt_addresses属性的客户端模型,其中包含客户端地址的几种不同可能格式。然后我有一个引用模型(如果您熟悉本地SEO,您就会知道这个术语)。引用模型没有地址字段,但它有一个nap_correct?字段(NAP代表“姓名”、“地址”、“电话号码”)。如果CSV行的名称、地址和电话号码与我在该客户的数据库中拥有的相同,

  4. ruby , `match' : invalid byte sequence in UTF-8 - 2

    我对UTF-8编码有一些问题。我在这里阅读了一些帖子,但它仍然无法正常工作。这是我的代码:#!/bin/envruby#encoding:utf-8defdeterminefile=File.open("/home/lala.txt")file.eachdo|line|puts(line)type=line.match(/DOG/)puts('aaaaa')iftype!=nilputs(type[0])breakendendend这是我文件的前3行:;?lalalalal60000065535-1362490443-0000006334-0000018467-0000000041en

  5. ruby-on-rails - rails : render a collection of models using an specific html view - 2

    我有以下关于rails的简单问题。假设我有一个模型用户。在View中,如果我这样做:views/user/_user.html.erb中的文件View将为每个用户调用和打印。如何更改它以使用特定View?我需要这样的东西:User.all:template=>"user/_user_2ndview.html"%>有什么帮助吗?提前致谢 最佳答案 您可以使用collection选项:User.all,:partial=>"users/user2ndview",:as=>:user%>View必须放在views/users/_user2

  6. ruby-on-rails - ruby rails : How to sort a collection_select - 2

    我想按数据库表列“plays”对其进行排序/排序(按我想要的方式降序或升序)我完全糊涂了。刚刚找到了select而不是collection_select的解决方案?我的一些代码不知道如何排序/排序数据库表中还有一些列,如“plays”、“goals”... 最佳答案 只需将实际排序的集合传递给collection_select助手:collection_select(:post,:author_id,Author.order('created_atDESC'),:id,:name_with_initial,:prompt=>true

  7. ruby-on-rails - 如何使用 grouped_collection_select 显示多选? - 2

    我正在使用以下代码来显示类别的TreeView选择框:grouped_collection_select(:categories,:category_id,Category.top_level,:children,:name,:id,:name,:include_blank=>true)如何更改它以允许多选?此外,是否可以让它显示复选框而不是选择框? 最佳答案 尝试grouped_collection_select(:categories,:category_id,Category.top_level,:children,:name

  8. ruby-on-rails - Rails 3.2 ActiveAdmin 'Collection is not a paginated scope.' 错误 - 2

    我正在使用Rails3.2和ActiveAdmin0.4.4开发应用程序。我有一个名为Teaser的模型(/app/models/teaser.rb):classTeasertruemount_uploader:img,TeaserUploaderend然后我向其中添加了ActiveAdmin(/app/admin/teaser.rb):#encoding:UTF-8ActiveAdmin.registerTeaserdoformdo|f|f.inputs"Teaser"dof.input:name,:label=>'Текст'f.input:url,:label=>'Ссылка'

  9. Ruby String.encode 仍然给出 "invalid byte sequence in UTF-8" - 2

    在IRB中,我正在尝试以下操作:1.9.3p194:001>foo="\xBF".encode("utf-8",:invalid=>:replace,:undef=>:replace)=>"\xBF"1.9.3p194:002>foo.match/foo/ArgumentError:invalidbytesequenceinUTF-8from(irb):2:in`match'知道出了什么问题吗? 最佳答案 我猜"\xBF"已经认为它是用UTF-8编码的,所以当你调用encode时,它认为你正在尝试编码一个UTF-8中的UTF-8字符

  10. 漫谈测试成长之探索——测试排期 - 2

     《漫谈测试成长之探索——测试文档》一文阐述了我们可以从项目维度去整理测试相关的文档来提升自己,本文将从测试排期方面探索成长方向。我们知道,对于做一件事,我们要有计划,要知道目标,要记得看时间。这里的时间对应到软件测试中就是与测试相关的时间节点。如图1-1所示,在以往工作中,作为一线测试执行者,我们一般会关注开发计划提测时间、测试计划开始时间、测试计划完成时间和需求计划发布时间。但是,经验告诉我们,只关注这些时间节点似乎是不够的。在实际工作中,需求实际可测试的时间经常延期,测试时间被压缩的情况时有发生。图1-1传统测试排期时间节点那我们能做些什么去规避或者说减少测试工期被压缩的情况呢?本文的答

随机推荐