map
将数组中每个元素转换为其他元素,返回一个新的数组。
let fibs = [0, 1, 1, 2, 3, 5]
var squared: [Int] = []
for fib in fibs {
squared.append(fib * fib)
}
squared // [0, 1, 1, 4, 9, 25]
let squares = fibs.map{ fib in fib*fib }
squares // [0, 1, 1, 4, 9, 25]
特点:
1、它很短,长度短一般意味着错误少,它比原来更清晰,所有无关的内容都被移除了。
2、squared 将由 map 的结果得到,并且我们不会再改变它的值,所以也就不再需要用 var 来进行声明了,我们可以将其声明为 let。不再需要为 squared 显式地指明类型了。
3、创造 map 函数并不难,你只需要把 for 循环中的代码模板部分,用一个泛型函数封装起 来就可以了。
extension Array {
func map<T>(_ transform: (Element) -> T) -> [T] {
var result: [T] = []
result.reserveCapacity(count)//Reserves enough space to store the specified number of elements.
for x in self{
result.append(transform(x))
}
return result
}
}
使用函数将行为参数化
想一想 map 的实现,是什么让它如此通用而且有用?
map 设法将模板代码分离出来,这些模板代码并不会随着每次调用发生变动,发生变动的是那 些功能代码 —— 也就是如何变换每个元素的逻辑。map 通过把调用者所提供的变换函数作为参数来做到这一点。
所有这些函数的目的都是为了摆脱代码中那些杂乱无趣的部分,重点突出那些程序员真正想要表达的逻辑代码。
map 和 flatMap — 对元素进行变换。
filter — 只包含特定的元素。
allSatisfy — 针对一个条件测试所有元素。
reduce — 将元素聚合成一个值。
forEach — 访问每个元素。
sort(by:), sorted(by:), lexicographicallyPrecedes(_:by:), 和 partition(by:) — 重排元素。
firstIndex(where:), lastIndex(where:), first(where:), last(where:), 和
contains(where:) — 一个元素是否存在?
min(by:) 和 max(by:) — 找到所有元素中的最小或最大值。
elementsEqual(_:by:) 和 starts(with:by:) — 将元素与另一个数组进行比较。
split(whereSeparator:) — 把所有元素分成多个数组。
prefix(while:) — 从头取元素直到条件不成立。
drop(while:) — 当条件为真时,丢弃元素;一旦不为真,返回其余的元素 (和 prefix 类似,不过返回相反的集合)。
removeAll(where:) — 删除所有符合条件的元素。
allSatisfy
let names = ["Sofia", "Camilla", "Martina", "Mateo", "Nicolás"]
let allHaveAtLeastFive = names.allSatisfy({ $0.count >= 5 })
// allHaveAtLeastFive == true
sort sort(by:)
var students = ["Asd", "Abc", "Ace"]
students.sort() //["Abc", "Ace", "Asd"]
students.sort(by: >) //students == ["Asd", "Ace", "Abc"]
lexicographicallyPrecedes(_:by:)
let word1 = "bat"
let word2 = "cat"
let lex_words = word1.lexicographicallyPrecedes(word2)
print(lex_words) //result TRUE
let zebra = "zebra"
let giraffe = "giraffe"
let lex_zoo = zebra.lexicographicallyPrecedes(giraffe)
print(lex_zoo) //result FALSE
let firstSequence = [1,2,3]
let secondSequence = [4,5,6]
let lex_num = firstSequence.lexicographicallyPrecedes(secondSequence)
print(lex_num) //result TRUE
let thirdSequence = [1, 4, 5]
let fourthSequence = [1, 4, 5, 6]
let lex_num2 = thirdSequence.lexicographicallyPrecedes(fourthSequence)
print(lex_num2) //result TRUE
let firstDate = "01/01/2019"
let secondDate = "01/02/2019"
let lex_date = firstDate.lexicographicallyPrecedes(secondDate)
print(lex_date) //result TRUE
let newYear1 = "12/31/2019"
let newYear2 = "12/31/2018"
let lex_new_year = newYear1.lexicographicallyPrecedes(newYear2)
print(lex_new_year) //result FALSE
partition(by:)
var numbers = [50, 60, 80, 20, 100, 22, 10, 66, 30]
let p = numbers.partition(by: { $0 > 22 })
// p == 3
// numbers == [10, 22, 20, 80, 100, 60, 50, 66, 30]
elementsEqual
let a = 1...3
let b = 1...10
print(a.elementsEqual(b))//false
print(a.elementsEqual([1, 2, 3]))//true
print(a.elementsEqual([1, 3, 2]))//false
split
let line = "BLANCHE: I don't want realism. I want magic!"
print(line.split(whereSeparator: { $0 == " " }))
print(line.split(maxSplits: 3, whereSeparator: { $0 == " " }))
print(line.split(omittingEmptySubsequences: false, whereSeparator: { $0 == " " }))
["BLANCHE:", "I", "don\'t", "want", "realism.", "I", "want", "magic!"]
["BLANCHE:", "I", "don\'t", " want realism. I want magic!"]
["BLANCHE:", "", "", "", "I", "", "don\'t", "", "want", "realism.", "I", "want", "magic!"]
如果在你的代码中,发现多个地方都有遍历一个数组并做相同或类似的事情时,可以考虑给 Array 写一个扩展。比如,下面的代码将数组中的元素按照相邻且相等的方式拆分开
let array: [Int] = [1, 2, 2, 2, 3, 4, 4]
var result: [[Int]] = array.isEmpty ? [] : [[array[0]]]
for (previous, current) in zip(array, array.dropFirst()) {
if previous == current {
result[result.endIndex-1].append(current)
}else{
result.append([current])
}
}
//[[1], [2, 2, 2], [3], [4, 4]]
extension Array {
func split(where condition: (Element, Element) -> Bool) -> [[Element]] {
var result: [[Element]] = self.isEmpty ? [] : [[self[0]]]
for (previous, current) in zip(self, self.dropFirst()) {
if condition(previous, current) {
result.append([current])
} else {
result[result.endIndex-1].append(current)
}
}
return result
}
}
let array: [Int] = [1, 2, 2, 2, 3, 4, 4]
let parts = array.split { $0 != $1 }
let parts2 = array.split(where: !=)
parts, parts2// [[1], [2, 2, 2], [3], [4, 4]]
相较 for 循环,split(where:) 版本的可 读性更好。虽然 for 循环也很简单,但是在你的头脑里始终还是要去做个循环,这加重了理解 的负担。使用 split(where:) 可以减少出错的可能性,而且它允许你使用 let 而不是 var 来声明 结果变量
可变和带有状态的闭包
array.map { item in
table.insert(item)
}
let a = [1, 2, 3].map { item in
}
print(a)//[(), (), ()]
let b = a.first
print(b)//Optional(())
这将副作用 (改变了查找表) 隐藏在了一个看起来只是对数组变形的操作中。如果你看到类似上 面这样的代码,使用简单的 for 循环显然是比使用 map 这样的函数更好的选择。在这种情况下, forEach 方法也比 map 更合适。
这样带有副作用的做法,和故意给闭包一个局部状态有本质不同,后者是一种非常有用的技术。 闭包是指那些可以捕获和修改自身作用域之外的变量的函数,当它和高阶函数结合时也就成为 了一种强大的工具。
extension Array {
func accumulate<Result>(_ initialResult: Result,
_ nextPartialResult: (Result, Element) -> Result) -> [Result] {
var running = initialResult
return map { next in
running = nextPartialResult(running, next)
return running
}
}
}
let accumulateArray = [1,2,3,4].accumulate(0) { (result, element) -> Int in
result + element
}
let accumulateArray = [1,2,3,4].accumulate(0, +)
accumulateArray//[1, 3, 6, 10]
filter
从数组中筛选出符合条件的元素并返回新的数组
extension Array {
func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
var result: [Element] = []
for x in self where isIncluded(x) {
result.append(x)
}
return result
}
}
let nums = [1,2,3,4,5,6,7,8,9,10]
nums.filter { num in num % 2 == 0 } // [2, 4, 6, 8, 10]
nums.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10]
对于很短的闭包来说,这样做有助于提高可读性。但是如果闭包比较复杂的话,更好的做法应 该是像我们之前那样,显式地把参数名字写出来。不过这更多的是一种个人选择,使用一眼看 上去更易读的版本就好。一个不错的经验是,如果闭包可以很好地写在一行里的话,那么使用 简写会更合适。
通过组合使用 map 和 filter,我们现在可以轻易完成很多数组操作,而不需要引入中间变量。
//寻找所有 100 以内的偶平方数
(1..<10).map { $0 * $0 }.filter { $0 % 2 == 0 } // [4, 16, 36, 64]
//耗性能
bigArray.filter { someCondition }.count > 0
//正确
bigArray.contains { someCondition }
只应该在需要所有结果时才去选择使用 filter。
reduce
将数组中所有元素合并为一个新的单一的值,并返回。
map 和 filter 都作用在一个数组上,并产生另一个新的、经过修改的数组。
extension Array {
func reduce<Result>(_ initialResult: Result,
_ nextPartialResult: (Result, Element) -> Result) -> Result {
var result = initialResult
for x in self {
result = nextPartialResult(result, x)
}
return result
}
}
let fibs = [0, 1, 1, 2, 3, 5]
var total = 0
for num in fibs {
total = total + num
}
total // 12
let sum = fibs.reduce(0) { total, num in total + num } // 12
let sum = fibs.reduce(0, +) // 12
fibs.reduce("") { str, num in str + "\(num), " } // 0, 1, 1, 2, 3, 5,
一个关于性能的小提示:reduce 相当灵活,所以在构建数组或者是执行其他操作时看到 reduce 的话不足为奇。
extension Array {
func map2<T>(_ transform: (Element) -> T) -> [T] {
return reduce([]) {
$0 + [transform($1)]
}
}
func filter2(_ isIncluded: (Element) -> Bool) -> [Element] {
return reduce([]) {
isIncluded($1) ? $0 + [$1] : $0
}
}
}
这样的实现符合美学,并且不再需要那些啰嗦的命令式 for 循环。但 Swift 不是 Haskell,Swift 的数组并不是列表 (list)。在这里,当每次通过追加一个变换过的或符合条件的元素到上一次的结果,来执行合并操作时,就会创建一个全新的数组。这意味着上面两个实现的复杂度是 O(n^2),而不是 O(n)。随着数组长度的增加,执行这些函数所消耗的时间将以平方关系增加。
reduce 还有另外一个版本,它的类型有所不同。具体来说,负责将中间结果和一个元素合并的 函数,现在接受一个 inout 的 Result 作为参数:
public func reduce<Result>(into initialResult: Result, _ updateAccumulatingResult:
(_ partialResult: inout Result, Element) throws -> () ) rethrows -> Result
可以把 inout Result 看作是一个可变的参数:我们可以在函数内部更改它
extension Array {
func filter3(_ isIncluded: (Element) -> Bool) -> [Element] {
return reduce(into: []) { result, element in
if isIncluded(element) {
result.append(element)
}
}
}
}
当使用 inout 时,编译器不会每次都创建一个新的数组,这样一来,这个版本的 filter 时间复杂 度再次回到了 O(n)。当 reduce(into:_:) 的调用被编译器内联时,生成的代码通常会和使用 for 循环所得到的代码是一致的。
一个展平的 map
let numbers = [1, 2, 3, 4]
let mapped = numbers.map { Array(repeating: $0, count: $0) }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
flatMap 的函数签名看起来和 map 基本一致,只是它的变换函数返回的是一个数组。在实现 中,它使用 append(contentsOf:) 代替了 append(_:),这样返回的数组是展平的了
extension Array {
func flatMap<T>(_ transform: (Element) -> [T]) -> [T] {
var result: [T] = []
for x in self {
result.append(contentsOf: transform(x))
}
return result
}
}
var numbers = [1, 2, 3, 4, 5]
numbers.append(contentsOf: 10...15)
print(numbers)
// Prints "[1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15]"
flatMap 的另一个常见使用情景是将不同数组里的元素进行合并。为了得到两个数组中元素的 所有配对组合,我们可以对其中一个数组进行 flatMap,然后在变换函数中对另一个数组进行 map 操作
let suits = ["", "", "", ""]
let ranks = ["J","Q","K","A"]
let result = suits.flatMap { suit in
ranks.map { rank in
(suit, rank)
}
}
//[("", "J"), ("", "Q"), ("", "K"), ("", "A"),
("❤", "J"), ("❤", "Q"), ("❤", "K"), ("❤", "A"),
("", "J"), ("", "Q"), ("", "K"), ("", "A"),
("", "J"), ("", "Q"), ("", "K"), ("", "A")]
使用 forEach 进行迭代
它和 for 循环的工作方式非常类似:把传入的函数对序列 中的每个元素执行一次。
和 map 不同,forEach 不返回任何值,特别适合执行那些带副 作用的操作。
for element in [1,2,3] {
print(element)
}
[1,2,3].forEach { element in
print(element)
}
如果你想要对集合中的每个元素都调用一个函数的话,使用 forEach 会比较合适。
相比于传递一个闭包表达式来说,传递一个函数名给 forEach 可以使代码更加简 洁和紧凑。
[button, label, view].forEach(view.addSubview)
当一个 for 循环中有 return 语句时,将它重写为 forEach 会造成代码行为上的极大区别
extension Array where Element: Equatable {
func firstIndex(of element: Element) -> Int? {
for idx in self.indices where self[idx] == element {
return idx
}
return nil
}
func firstIndex_foreach(of element: Element) -> Int? {
self.indices.filter { idx in
self[idx] == element
}.forEach { idx in
return idx
}
return nil
}
}
在 forEach 中的 return 并不能让外部函数返回,它仅仅只是让闭包本身返回
(1..<10).forEach { number in
print(number)
if number > 2 {
return
}
}
//1 2 3 4 5 6 7 8 9
比如上面的 addSubview 的例子里,forEach 可能会比 for 循环更好。不过,因 为 return 在其中的行为不太明确,我们建议大多数其他情况下不要用 forEach。这种时候,使 用常规的 for 循环可能会更好。
//迭代实现斐波那契数列
func fab_iteration(index:Int) -> Int {
guard index > 0 else {
return 0
}
if index == 1 || index == 2 {
return 1
} else {
var f1 = 1
var f2 = 1
var f3 = 0
for _ in 0..<index - 2 {
f3 = f1 + f2
f1 = f2
f2 = f3
}
return f3
}
}
fab_iteration(index: 0)
fab_iteration(index: 1)
fab_iteration(index: 2)
fab_iteration(index: 3)
fab_iteration(index: 4)
fab_iteration(index: 11)
fab_iteration(index: 12)
fab_iteration(index: 13)
//递归实现斐波那契数列
func fab_recursion(index:Int) -> Int {
guard index > 0 else {
return 0
}
if index == 1 || index == 2{
return 1
}else{
//递归求值,自己调用自己
return fab_recursion(index: index - 1) + fab_recursion(index: index - 2)
}
}
fab_recursion(index: 0)
fab_recursion(index: 1)
fab_recursion(index: 2)
fab_recursion(index: 3)
fab_recursion(index: 4)
fab_recursion(index: 11)
fab_recursion(index: 12)
fab_recursion(index: 13)
数组切片
let fibs = [0, 1, 1, 2, 3, 5]
let slice = fibs[1...]
slice // [1, 1, 2, 3, 5]
type(of: slice) // ArraySlice<Int>
//将数组切片转成数组
let newArray = Array(slice)
type(of: newArray) // Array<Int>
切片类型只是数组的一种表示方式,它背后的数据仍 然是原来的数组,只不过是用切片的方式来进行表示。因为数组的元素不会被复制,所以创建 一个切片的代价是很小的。
切片和它背后的数组是使用相同的索引来引用元素的。因此,切片索引不需要从零开始。
错误地访问 slice[0] 元素会使我们的程序因越界而崩溃,建议你总是基于 startIndex 和 endIndex 属性做索引计算。
字典
字典通过键获取值所花费的平均时间是常数量级的。
数组中搜寻一个特定元素所花费的时间将与数组尺寸成正比。
字典是无序的,使用 for 循环来枚举字典中的键值对时,顺序是不确定的。
数组是有序的。
字典使用下标来得到某个设置的值,字典查找会返回一个可选值,指定的键不存在时,返回 nil。
数组中,使用越界下标进行访问将会导致程序崩溃。
对数组来说,你很少需要直接使用数组的索引。即使你用到索引,这个索引也一般是通过某些方式计算得来的 (比如从 0..<array.count 这样的范围内获取到)。也就是说,使用一个无效 索引一般都是程序员的失误。
字典的键往往是从其他渠道得来的,从字典本身获 取键反而十分少见。
可变性
用 let 定义的字典是不可变的,用 var 定义一个可变字典。
移除字典的一个值:
- 用下标将对应的值设为 nil。
-
removeValue(forKey:)
,会将被删除的值返回 (如果待删除的键不 存在,则返回 nil)。
更新字典内容:
-
updateValue(_:forKey:)
,如果之前键已经存在的话,这个方法 会返回更新前的值。
一些有用的字典方法
合并两个字典,用来做合并的字典需要覆盖重复的键:
-
merge(_:uniquingKeysWith:)
,参数1是要进行合并的键 值对,参数2是定义如何合并相同键的两个值的函数。 merging(_:uniquingKeysWith:)
var dic = ["first": "A", "Second": "B"]
dic.merge(["Third": "C"], uniquingKeysWith: { $1 })
//["first": "A", "Second": "B", "Third": "C"]
dic.merge(["first": "D"], uniquingKeysWith: { $1 })
//["first": "D", "Second": "B", "Third": "C"]
dic.merge(["first": "E"], uniquingKeysWith: { $0 + $1 })
//["Second": "B", "Third": "C", "first": "DE"]
var dic = ["first": "A", "Second": "B"]
dic.merge(["Third": "C"]) { (str0, _) in str0 }
//["first": "A", "Second": "B", "Third": "C"]
Dictionary(_, uniquingKeysWith:)
let arr = [("h", 1), ("e", 1), ("l", 1), ("l", 1), ("o", 1)]
let dic = Dictionary(arr, uniquingKeysWith: +)
//["e": 1, "o": 1, "l": 2, "h": 1]
let arr = [("h", "value"), ("e", "value"), ("h", "value1")]
let dic = Dictionary(arr, uniquingKeysWith: +)
//["h": "valuevalue1", "e": "value"]
mapValues
enum Setting {
case text(String)
case int(Int)
case bool(Bool)
}
let settings: [String: Setting] = [ "Airplane Mode": .bool(false),
"Name": .text("My iPhone")]
let settingsAsStrings = settings.mapValues { setting -> String in
switch setting {
case .text(let text): return text
case .int(let number): return String(number)
case .bool(let value): return String(value)
}
}
//["Airplane Mode": "false", "Name": "My iPhone"]
Set
集合是一组无序的元素, 每个元素只会出现一次。你可以将集合想像为一个只存储了键而没有存储值的字典。
使用集合的2种情况:
- 需要高效地测试某个元素是否存在于序列中并且元素的顺序不重要时。
- 需要保证序列中不出现重复元素时。
创建方法:可以用数组字面量的方式初始化一 个集合
let naturals: Set = [1, 2, 3, 2]
naturals // [1, 2, 3]
naturals.contains(3) // true
naturals.contains(0) // false
集合代数
- 补集
subtracting
let iPods: Set = ["iPod touch", "iPod nano", "iPod mini", "iPod shuffle", "iPod Classic"]
let discontinuedIPods: Set = ["iPod nano", "iPod mini", "iPod shuffle", "iPod Classic"]
let currentIPods = iPods.subtracting(discontinuedIPods) // ["iPod touch"]
let currentIPods1 = discontinuedIPods.subtracting(iPods) // []
- 交集
intersection
let iPods: Set = ["iPod nano", "iPod touch", "iPod mini", "iPod shuffle", "iPod Classic"]
let touchscreen: Set = ["iPod nano", "iPod touch", "iPhone", "iPad"]
let iPodsWithTouch = iPods.intersection(touchscreen)// ["iPod nano", "iPod touch"]
- 并集
formUnion
let discontinuedIPods: Set = ["iPod mini", "iPod Classic", "iPod nano", "iPod shuffle"]
var discontinued: Set = ["iBook", "Powerbook", "Power Mac"]
discontinued.formUnion(discontinuedIPods)
//["iPod mini", "iPod Classic", "Powerbook", "iPod nano", "iBook", "iPod shuffle", "Power Mac"]
discontinued = discontinued.union(discontinuedIPods)
//["iPod mini", "iPod Classic", "Powerbook", "iPod nano", "iBook", "iPod shuffle", "Power Mac"]
索引集合IndexSex
和字符集合CharacterSet
IndexSet
表示了一个由正整数组成的集合。可以用 Set<Int>
来做这件事,但是 IndexSet
更加高效,因为它内部使用了一组范围列表进行实现。
比较:有一个含有 1000 个元素的 tableView,你想要一个集合来管理已经被用户选中的元素的索引。
Set<Int>
:根据选中的个数不同,最多可能会要存储 1000 个元素。
IndexSet
:会存储连续的范围,在选取前 500 行的情况下,IndexSet 里其实只存储了选择的 首位和末位两个整数值。
var indices = IndexSet()
indices.insert(integersIn: 1..<5)
indices.insert(integersIn: 11..<15)
let evenIndices = indices.filter { $0 % 2 == 0 }
//evenIndices [2, 4, 12, 14]
CharacterSet
是一个高效的存储 Unicode 编码点 (code point) 的集合。它经常被用来检查一个特定字符串是否只包含某个字符子集。
let hw = "Hello world"
let numbers = CharacterSet(charactersIn: "123456789")
let hello = CharacterSet(charactersIn: "Hello")
let numbersRange = hw.rangeOfCharacter(from: numbers) //nil
let helloRange = hw.rangeOfCharacter(from: hello)
使用rangeOfCharacter(from:)
来判断String对象是否包含特定的字符,如果包含,会返回一个Range对象,否则,返回nil。
在闭包中使用集合
获取序列中所有的唯一元素,并保持顺序。
extension Sequence where Element: Hashable {
func unique() -> [Element] {
var seen: Set<Element> = []
return filter { element in
if seen.contains(element) {
return false
} else {
seen.insert(element)
return true
}
}
}
}
[1,2,3,12,1,3,4,5,6,4,6].unique() // [1, 2, 3, 12, 4, 5, 6]
找到序列中的所有不重复的元素,并且通过元素必须满足 Hashable 这个约束来维持它们原来的顺序。
Hashable
苹果官方介绍 https://developer.apple.com/documentation/swift/hashable
- Set 要求成员类型要遵循 Hashable,遵循 Hashable 的字符串可以存入 Set,没有遵循 Hashable 的 CGPoint 无法存入。
struct Set<Element> where Element : Hashable
struct Dictionary<Key, Value> where Key : Hashable
let point1 = CGPoint(x: 10, y: 10)
let point2 = CGPoint(x: 20, y: 20)
let set1 = Set(arrayLiteral: 1, 2)//Set<Int>
let set2 = Set(arrayLiteral: point1, point2)
//Generic struct 'Set' requires that 'CGPoint' conform to 'Hashable'
- hashValue
当一个类型遵循 Hashable 時,会产生一个hashValue。可利用hashValue判断是否是同一信息。
//String遵循 Hashable
let name1 = "abc"
let name2 = "def"
let name3 = "abc"
print(name1.hashValue)//-5493371058385831390
print(name2.hashValue)//4014202544081776331
print(name3.hashValue)//-5493371058385831390
withUnsafePointer(to: &name1) { ptr in print(ptr) }
withUnsafePointer(to: &name2) { ptr in print(ptr) }
withUnsafePointer(to: &name3) { ptr in print(ptr) }
//0x0000000103271520
//0x0000000103271530
//0x0000000103271540
- Set & Hashable
Set 希望它的成员独一无二,不要有重复,因此它要求成员必须遵循 Hashable,成员的 hash value 可帮它判断成员是否重复。
let name1 = "abc"
let name2 = "def"
let name3 = "abc"
print(name1.hashValue)//-5493371058385831390
print(name2.hashValue)//4014202544081776331
print(name3.hashValue)//-5493371058385831390
var set = Set(arrayLiteral: name1, name2)
set.insert(name3)
print(set)//["abc", "def"]
- 让自定义类型遵循Hashable
Swift很多类型遵循Hashable,比如String,Int,Double 等。我们也可以让自定义类型遵循 Hashable。
如以下例子,当 struct 的 property 都遵循 Hashable 时,我们只要让它遵循 Hashable,不用自己定义 Hashable 的 function hash(into:),因为 Swift 可以帮我們产生。
struct person: Hashable {
var name: String
var height: Float
}
let person1 = person(name: "a", height: 175)
let person2 = person(name: "b", height: 180)
print(person1.hashValue)//-4953857647778798611
print(person2.hashValue)//7111694992685318703
- enum 定义的类型默认遵循Hashable
enum PersonType {
case goodGuy
case badGuy
}
let type1: PersonType = .goodGuy
let type2: PersonType = .badGuy
print(type1.hashValue)//-5789570240387514821
print(type2.hashValue)//6255626738280473634
以下情况需要自己定义 hash(into:)
- 1、我们想自己决定如何产生 hashValue
- 2、当 struct 的 property 不遵循Hashable
- 3、是class类型
//我们想自己决定如何产生 hashValue
struct Person: Hashable {
var name: String
var height: Double
var gender: String
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(gender)
}
}
var person1 = Person(name: "a", height: 180, gender: "Male")
var person2 = Person(name: "a", height: 175, gender: "Male")
var person3 = Person(name: "a", height: 160, gender: "Female")
print(person1.hashValue)//339655747645164896
print(person2.hashValue)//339655747645164896
print(person3.hashValue)//4116782432614641433
//当删除hash(into:)再次打印
print(person1.hashValue)//7503100225942796219
print(person2.hashValue)//-6684814023545868142
print(person3.hashValue)//-6629419078928364845
当property 都遵循Hashable时,Swift 会自动帮我们生成Equatable 的 function ==,我们不用自己定义。
当property 不遵循 Hashable时,我们要自己定义function ==
因为protocol Hashable 本身又遵循Equatable,protocol Hashable : Equatable
struct Pet {
var name: String
}
struct Person: Hashable {
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.pet.name == lhs.pet.name
}
var name: String
var pet: Pet
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(pet.name)
}
}
class 类型,同理需实现hash(into:)和==
class TestClass: Hashable {
static func == (lhs: TestClass, rhs: TestClass) -> Bool {
return lhs.name == rhs.name && lhs.height == lhs.height
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(height)
}
let name: String
let height: Float
init(name: String, height: Float) {
self.name = name
self.height = height
}
}
let test1 = TestClass(name: "a", height: 175)
let test2 = TestClass(name: "b", height: 180)
print(test1.hashValue)//-367417914791106167
print(test2.hashValue)//-3985566457652956018
Rang
范围代表的是两个值的区间,它由上下边界进行定义。
..<
创建一个不包含上边界的半开范围
Range
...
创建同时包含上下边界的闭合范围
ClosedRange
let singleDigitNumbers = 0..<10
//Range<Int> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let lowercaseLetters = Character("a")...Character("z")
//ClosedRange<Character> a...z
let fromZero = 0...
//PartialRangeFrom<Int>(lowerBound: 0)
let upToZ = ..<Character("z")
//PartialRangeUpTo<Character>(upperBound: "z")
//基本的操作是检测它是否包含了某些元素:
singleDigitNumbers.contains(9) // true
lowercaseLetters.overlaps("c"..<"f") // true
只有半开范围能表达空间隔 (也就是下界和上界相等的情况,比如 5..<5)
let rang = 5..<5
for v in rang {
print(v)
}
let max = rang.max()//nil
只有闭合范围能包括其元素类型
所能表达的最大值 (比如 0...Int.max)。
而半开范围则要求范围上界是一个比自身所包含的最大值还要大 1 的值。
可数范围
不是所有的范围都可以使用for in方式遍历,编译器不允许我们遍历一个 Character 的范围。
原因:'Character' 类型没有实现 'Strideable' 协议。
让 Range 满足集合类型协议需要满足2个条件:
- 它的元素需要满足
Strideable
协议 (你可以通过增加偏移来从一个元素移动到另一个)。 - 步长
stride step
是整数。
范围表达式
所有这五种范围都满足 RangeExpression 协议。
它允许我们询问某个元素是否被包括在该范围中。
给定一个集合类型,它能够计算出表达式所指定的完整的 Range。
let arr = [1,2,3,4]
arr[2...] // [3, 4]
arr[..<1] // [1]
arr[1...2] // [2, 3]
arr[...] // [1, 2, 3, 4]
type(of: arr) // Array<Int>
type(of: arr[2...]) // ArraySlice<Int>
relative(to:)
let numbers = [10, 20, 30, 40, 50, 60, 70]
let upToFour = ..<4
let r1 = upToFour.relative(to: numbers)
// r1 == 0..<4
let r2 = upToFour.relative(to: numbers[2...])
// r2 == 2..<4
let r3 = upToFour.relative(to: [20, 60])
//r3 == 0..<4
网友评论