说说 Swift 中的集合协议--Pt.2

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战


Collection.png

引言

在上篇文章 说说 Swift 中的集合协议--Pt.1 中,我们把集合树中的 IteratorProtocol、Sequence、Collection 过了一遍,今天就来探索一下剩余的部分。


BidirectionalCollection

BidirectionalCollection 支持向后和向前遍历。它可以从任何有效索引(不包括集合的 startIndex)向后遍历。因此,它可以提供一些额外的操作,比如,可以快速的访问集合的最后一个元素。此外,它对某些序列和集合方法有更高效的实现,比如,reverse()。协议定义也很简单~

protocol BidirectionalCollection: Collection {
    func index(before i: Index) -> Index
}
复制代码

因为 BidirectionalCollection 可以向后遍历,所以可以改进 reversed() 方法的实现。reversed() 最初是在 Sequence 里实现的,返回类型为 Array<element>,O(n) 的时间复杂度。

BidirectionalCollection 实现了 reverse(),它不会去读取 Sequence 元素,而是返回一个名为 ReversedCollection 的新类型,该类型包装了原始的 Collection。ReversedCollection 将自己的索引转换为基本集合的索引,并根据需要从基本集合中访问元素。创建 ReversedCollection 是 O(1) 的时间复杂度。

由于 Sequence 实现的返回类型与 BidirectionalCollection 的返回类型不同,我们可以看到在 Xcode 有两个 reversed() 的自动提示。

image.png

如果没有声明变量的类型,则默认得到的是 ReversedCollection 类型,所以我们可以通过显式声明变量的类型,告诉编译器我们想要哪个reversed()方法。

image.png

现在我们可以从两端访问元素,但还不能改变指定索引处的值,该功能由 MutableCollection 提供。


MutableCollection

如果我们尝试使用下标修改集合中的任何值,将会出现编译错误。集合只为我们提供对其元素的下标读访问。为了能写访问,我们需要遵循 MutableCollection。协议定义很简单~

protocol MutableCollection: Collection {
    subscript(position: Index) -> Element { get set }
}
复制代码

MutableCollection 看起来像是一个简单的附加功能,但是它提供了一些有用的方法,比如交换两个索引处的元素:swapAt(_: :) 和直接修集合本身的 reverse()。

在实现 MutableCollection 时需要注意,下标赋值不应该改变集合本身的长度。这就是为什么 String 不符合 MutableCollection 的原因。使用下标替换字符可以改变字符串的长度,因为字符本身没有固定的长度。

正式因为有长度限制,所以 MutableCollection 不提供诸如 remove(_:)、 append(_:)、 insert(_: at:) 等方法,这些方法通常与单词 mutable 相关联。

Collection 可以让我们使用下标访问任何元素,但访问的时间不是固定的。这里就引出了 RandomAccessCollection 协议,它可以让我们以固定的时间访问集合中的任何元素。


RandomAccessCollection

它是一个支持快速随机访问元素的集合。RandomAccessCollections 可以以 O(1) 的时间复杂度把下标移动到任何距离及计算两个索引之间的距离。因此,RandomAccessCollection 和 BidirectionalCollection 之间的根本区别在于,索引移动或索引间的计算等操作,RandomAccessCollection 效率更高。协议定义非常简单~

public protocol RandomAccessCollection: BidirectionalCollection
where SubSequence: RandomAccessCollection, Indices: RandomAccessCollection { }
复制代码

除了要求 BidirectionalCollection 的子序列和索引类型本身是 RandomAccessCollection 外,它没有增加额外的实现。

遵循 RandomAccessCollection,虽然不能解锁一些额外技能,但它确实提高了某些现有算法的性能,比如 dropFirst(_:)、 dropLast(_:)、prefix(_:), suffix(_:),这些都是O(1)时间复杂度。最重要的是,访问 count 属性的时间复杂度为 O(1) ,而不需要对整个集合进行迭代。如果我们的 RandomAccessCollection 也符合 MutableCollection,那么我们也可以调用 sort() 和 shuffle() 方法直接修改集合本身。

尽管遵循 MutableCollections的集合是可变集合,但仍然不能调用像 insert(_: at:) 这样的改变长度的函数。想改变长度,就得遵循 RangeReplaceableCollection 协议。


RangeReplaceableCollection

RangeReplaceableCollection 支持用另一个集合的元素替换任意范围的元素。听起来有点像 MutableCollection,但是这里的关键区别是新集合不需要与被替换的集合具有相同的长度。协议定义很简单~

protocol RangeReplaceableCollection: Collection {
    init()
    mutating func replaceSubrange<C>(_ subrange: Range<Index>, with newElements: C)
    where C: Collection, C.Element == Element
}
复制代码

这个方法解锁了一系列新功能。我们可以在任何索引处插入一个新的集合,通过 index..<index 作为子区域。我们可以在最后添加一个集合,完成在 endIndex 插入集合并从集合中删除所有元素。

由于 rangereplaceablection 可以修改集合的长度,因此 String 可以安全地遵循它。

Dictionaries and Sets

  • Set 遵循了 Collection 协议,这使得它们可以访问类似 firstIndex(of:) 等方法。对于像 Set 这样的无序集合,此方法虽然没啥意义,但是我猜想,与一些 API 不一致性相比,遵循 Collection 的好处更大。

  • Dictionary 也遵循了 Collection 协议。基于使用键作为下标访问值的方式。Dictionary 实际上使用内部类型作为它们的 Index,而它们的 Element 类型是 tuple(Key,Value)。Dictionary 实际上有两个下标,一个基于内部索引来满足 Collection 协议的要求,另一个使用我们熟悉的 Key。

结语

以上就是集合协议的全部内容了,通过这两篇文章的介绍,相信大家也对集合协议有了一个整体上的认识,至少知道了集合可以遍历是遵循了什么协议,集合可以访问元素是遵循了什么协议,可以插入、删除元素是遵循了什么协议等等。

Guess you like

Origin juejin.im/post/7034755214334754847