说起区间,大家学过数学的应该都很清楚了,比如闭区间、开区间、半开半闭区间。今天为啥子说起Swift
里的Range
呢,其实也是闲来无事,想把截取String
弄的简单一点,然后涉及到一些Range
的使用,所以才有了后面的废话。
Swift
不知不觉已经到了5.0
了,过去如果想截取String
的话,还有个subString(...)
方法,但是现在这个方法已经标记为废弃状态了。但是从4.0
开始,Swift
提供了下面的方法来截取一个区间的字符串
@inlinable public subscript(r: Range<String.Index>) -> Substring { get }
这个方法唯一不方便的就是Range
的Bound
竟然是String.Index
,害得我们还要自己去获取一个String.Index
的对象,用起来多少有些不方便。我想要的很简单,实现一些Bound
是Int
类型的subscript
方法就可以了,完成后可以像下面这样,直接截取String
let str = "swift is the best language"
print(str[0...2]) // swi
print(str[0..<2]) // sw
.....
还有几种就省略了。开始自定义 subscript
方法之前,我们先来看下下面几种Range
的源码解释,以及他们的一些方法
1. Range(半开区间)
public struct Range<Bound> where Bound : Comparable {
public let lowerBound: Bound
public let upperBound: Bound
// ... ...
}
后面所有的Range
都包含两个属性:表示最小边界值的lowerBound
和表示最大边界值的upperBound
,这里的Bound
是遵守了Comparable
的一个associatedtype
,专门用来表示区间的边界值
associatedtype Bound : Comparable
关于Range
的描述是这样的
A half-open interval from a lower bound up to, but not including, an upper bound.
You create aRange
instance by using the half-open range operator (..<
).let underFive = 0.0..<5.0
可见Range
表示的是半开区间,并且它可以用来表示一个空的间隔序列,比如这样
let emptyRange = 10..<10
emptyRange.contains(10) // false
emptyRange.isEmpty // true
这里还有另外两个方法:
@inlinable public func clamped(to limits: Range<Bound>) -> Range<Bound>
@inlinable public func overlaps(_ other: Range<Bound>) -> Bool
@inlinable public func contains(_ element: Bound) -> Bool
clamped(...)
用来取两个区间的重叠区间,比如10..20.clamped(to: 15...100)
结果是15...20
,如果两个区间没有重叠部分,则返回一个空的区间
overlaps(...)
则用来表示两个区间是否有重叠部分。
contains(...)
用来判断区间是否包含某个值
2. ClosedRange(闭区间)
ClosedRange
的属性和方法和Range
是一样的,ClosedRange
和Range
的区别是包含最大边界值,并且它不能表达空区间
这种概念
3. CountableRange 和 CountableClosedRange
这两个 Range是继承自Range
和ClosedRange
的两个区间类,同样他们分别表示半开区间和闭区间,不过,他们都遵守了Strideable
协议。
public typealias CountableRange<Bound> = Range<Bound> where Bound : Strideable, Bound.Stride : SignedInteger
public typealias CountableClosedRange<Bound> = ClosedRange<Bound> where Bound : Strideable, Bound.Stride : SignedInteger
A type representing continuous, one-dimensional values that can be offset and measured.
Strideable协议要表达的意思是一些连续的一维的数据,比如一串数字 12345...
,这串数字能够执行偏移和测量这样的动作。我们来看下它约定的方法就明白什么意思了
func distance(to other: Self) -> Self.Stride
这个方法用来计算连续数值中两个数值之间的间隔(measure)
func advanced(by n: Self.Stride) -> Self
这个方法则表示一个数值增加一定的偏移量后得到的另一个数值(offset)
这两个方法就保证了遵守Strideable
协议就可以进行迭代操作,比如正常情况下,如果让你遍历0.1到2.3这个区间,你该怎么遍历呢?显然是没法遍历的,但是一旦Float
遵守了Strideable
协议,这件事情就好办了。因为一旦知道了步长以及根据步长得到下一个值的方法,你就可以把一个区间遍历完。
遵守了Strideable
协议就可以使用stride(from:to:by:)
和stride(from:through:by:)
来迭代一个序列了。比如,下面是源码中的一个小例子:
for radians in stride(from: 0.0, to: .pi * 2, by: .pi / 2) {
let degrees = Int(radians * 180 / .pi)
print("Degrees: \(degrees), radians: \(radians)")
}
// Degrees: 0, radians: 0.0
// Degrees: 90, radians: 1.5707963267949
// Degrees: 180, radians: 3.14159265358979
// Degrees: 270, radians: 4.71238898038469
最后一个参数(Stride
)表示步长,即两个相邻数值之间的距离
当一个Range的Bound是integers或者遵守了Strideable协议并且Stride是integer的时候,这个Range就可以用在for..in里,并且可以使用任何Sequence或者Collection的方法
4. PartialRangeUpTo、PartialRangeThrough和PartialRangeFrom
这三个Range都是表示部分区间的,
PartialRangeUpTo
: 操作符 ..<
比如 ..<5.0
表示数轴上5.0左边的区间,但不包括5.0这个值
PartialRangeThrough
: 操作符 ...
比如 ...5.0
表示数轴上5.0左边的区间,包括5.0这个值
PartialRangeFrom
: 操作符 ...
比如 5.0...
表示从数轴上从5.0开始的右边的区间,包含5.0这个值
下面,我们用上面这些 Range
来实现文章开始提到的截取String
的快捷功能:
// "swift"[0...2] // swi
subscript(value: CountableClosedRange<Int>) -> Substring {
get {
let start = index(self.startIndex, offsetBy: value.lowerBound)
let end = index(self.startIndex, offsetBy: value.upperBound)
return self[start ... end]
}
}
// "swift"[0..<2] // sw
subscript(value: CountableRange<Int>) -> Substring {
get {
let start = index(self.startIndex, offsetBy: value.lowerBound)
let end = index(self.startIndex, offsetBy: value.upperBound)
return self[start ..< end]
}
}
// "swift"[..<2] // sw
subscript(value: PartialRangeUpTo<Int>) -> Substring {
get {
return self[ ..<index(self.startIndex, offsetBy: value.upperBound)]
}
}
// "swift"[...2] // swi
subscript(value: PartialRangeThrough<Int>) -> Substring {
get {
return self[ ...index(self.startIndex, offsetBy: value.upperBound)]
}
}
// "swift"[2...] // ift
subscript(value: PartialRangeFrom<Int>) -> Substring {
get {
return self[index(self.startIndex, offsetBy: value.lowerBound)... ]
}
}
网友评论