范围代表的是两个值的区间,它由上下边界进行定义。你可以通过..<来创建一个不包含上边界的半开范围,或者使用...创建同时包含上下边界的闭合范围:
//0到9,不包含10
let singleDigitNumbers=0..<10
//包含"z"
let lowercaseLetters=Character("a")...Character("z")
范围看起来很自然地会是一个序列或者集合类型,但是可能出乎你的意料,它并非是这两者之一——至少不是所有的范围都是序列或者集合类型。
在标准库中,现在有四种范围类型。它们能够被归类到一个2×2的矩阵中:
矩阵中的列对应了我们上面提到的两种生成范围的操作符,它们分别创建半开的[Countable ]Range或者闭合的[Countable]ClosedRange。半开和闭合的范围各有所用:
·只有 半开范围 能够表达 空区间 的概念(当范围的上下边界相等时,比如5..<5)。
·只有 闭合范围 能够包含它的元素类型所能表达的最大值(比如0...Int.max)。半开范围总是最少会有一个值比范围所表达的值要大。
(在Swift2中,即使一个范围是由...操作符创建的,在技术上来说,所有的范围实际上都是半开的。标准库中曾经用了额外的HalfOpenInterval和ClosedInterval类型来弥补这个问题,不过在Swift3中它们被移除了。)
上表中的行区分了元素类型仅仅只是满足了Comparable的“普通”范围(这是范围元素的最小要求),以及那些元素满足Strideable 并且 使用整数作为步长的范围。只有后一种范围是集合类型,它继承了本章中所介绍的一系列强大的功能和方法。
Swift把这些功能更强的范围叫作 可数范围 (Countable Range),这是因为只有这类范围可以被迭代。对于可数范围,因为类型的Stride需要用整数进行约束,所以整数和指针类型是这类范围的有效的边界值,但是浮点类型不是。如果我们需要用连续的浮点值迭代某个可数范围,则需要使用stride(from:to:by)和stride(from:through:by)来创建一个这样的序列。也就是说,我们只能迭代某些范围。比如,上面定义的Character范围值就不是一个序列,下面的代码将无法工作:
for char in lowercaseLetters{
//...
}
//错误:ClosedRange<Character>类型不遵守Sequence协议
(对字符集进行迭代粗看起来应该没什么难度,但是实际却并非如此,这涉及Unicode的相关知识,后面会在关于字符串的章节中再深入这个问题。)
不过,下面的这种方式就没有问题,因为整数范围是一个可数范围,它是一个集合类型:
singleDigitNumbers.map{$0*$0}
//[0,1,4,9,16,25,36,49,64,81]
现在标准库需要将可数范围与其他普通的范围分开,成为CountableRange和CountableClosedRange。在理想状态下,它们不应该被区分对待,但是不这么做的话,就需要为泛型参数满足约束的Range和ClosedRange添加扩展,来让它们满足集合类型的要求。我们会在下一章详细讨论这个方面的内容,下面看看如果这么做,那么代码会是怎样的:
//Invalid in Swift 3
extension Range:RandomAccessCollection
where Bound:Strideable,Bound.Stride:SignedInteger
{
//实现RandomAccessCollection
}
Swift3的类型系统并不支持这样的表达方式,还不能针对泛型参数条件添加扩展,所以这里我们只能使用另外的类型。对于按照条件进行扩展的支持有望在Swift4中被加入,届时CountableRange和CountableClosedRange也将会被归类到Range和ClosedRange中。
半开的Range和闭合的ClosedRange之间的差异应该会一直存在,这个差异有时候会使得对范围的使用变得十分困难。假设你有一个方法接受Range<Character>作为参数,而同时你想要将上面创建的闭合的字符范围传递给它。这时候你会惊奇地发现,这是不可能完成的任务!可能你无法解释,为什么没有一种方法将ClosedRange转换为Range呢?如果想要将一个闭合范围转换为等效的半开范围,那么你就需要找到原来的闭合范围上界的后一个元素。除非元素本身满足Strideable,否则那是不可能的。而满足Strideable的元素所对应的则是可数范围。
也就是说,这个函数的调用者必须提供合适的类型。如果一个函数接受Range作为参数,那么你就不能用...来创建它。在实践中,我们不太确定这会带来多大的限制,因为大部分的范围都是基于整数的,不过可以肯定的是,这不太符合我们的直觉。
[1] https://github.com/apple/swift/blob/master/stdlib/public/core/Sequence.swift
[2] https://github.com/apple/swift-evolution/blob/master/proposals/0045-scan-takewhile-dropwhile.md
[3] https://github.com/apple/swift-evolution/blob/master/proposals/0116-id-as-any.md
[4] https://github.com/apple/swift-evolution/blob/master/proposals/0100-add-sequence-based-init-and-merge-to-dictionary. md
[5] https://zh.wikipedia.org/wiki/ 哈希表
[6] https://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html