【Swift进阶笔记】内建集合类型-数组
可变性
- 数组和标准库中的所有集合类型一样,是具有值语义的
var x = [1,2,3]
var y = x
y.append(4)
y // [1, 2, 3, 4]
x // [1, 2, 3]
想要改变一个数组,必须使用NSMutableArray,
但是NSArray的引用特性并不能保证这个数组不会被改变
let a = NSMutableArray(array: ["1", "2", "3"])
let b = a
a.insert("4", at: 3)
print(b)
(
1,
2,
3,
4
)
需要进行复制, 因为a声明是可变的
let c = NSMutableArray(array: ["1", "2", "3"])
let d = c.copy() as! NSArray
c.insert("4", at: 3)
print(d)
(
1,
2,
3
)
“在 Swift 中,数组只有一种统一的类型,可以通过在声明时使用 var 而非 let 来将数组定义为可变的。当你使用 let 声明第二个数组,并将第一个数组赋值给它时,可以保证这个新的数组是不会改变的,因为这里没有共用的引用。”
写实复制: 创建如此多的copy,会造成性能上的问题。所以引入了“写时复制”的技术。保证能够在必要的时候对数据进行复制。
y在append之前x, y共享内部的存储
索引
Swift不鼓励去做索引计算,因为这样做可能会引起潜在bug.无效的下标会造成可控的崩溃, 数组没有可选值选项。
-
迭代数组 for x in array
-
迭代除了第一个数组 for x in array.dropFirst()
-
迭代除了最后5个数组 for x in array.dropLast(5)
-
列举元素和对应的下标 for(num, element) in array.enumerated
-
寻找指定元素 if let index = array.firstIndex(where: { $0=="1" })
-
数组元素变形array.map { return $0 + "1"}
-
筛选出符合条件的元素array.filter { $0=="1" }
下面操作略有不同
- array.first(array.last 同理) 相当于
isEmpty ? nil : array[0]
-
removeLast & popLast & dropLast
-
removeLast空数组将造成崩溃
-
popLast删除空数组时会返回nil,删除元素时,会返回该元素
-
dropLast删除空数组时会返回nil,删除元素时,会返回剩余元素数组
-
变形
Map
var squared: [Int] = []
for fib in fibs {
squared.append(fib * fib)
}
squared // [0, 1, 1, 4, 9, 25]
map完成的操作
var squared: [Int] = []
for fib in fibs {
squared.append(fib * fib)
}
squared // [0, 1, 1, 4, 9, 25]
优势
-
长度短,代表错误少, 清晰
-
map相当于一个信号
-
map返回的数组用let声明
Map函数内部可能实现方式
extension Array {
func map<T>(_ transform: (Element)->T) -> [T] {
var result: [T] = []
result.reserveCapacity(count)
for x in self {
result.append(transform(x))
}
return result
}
}
Element是数组中包含元素类型的占位符,T是元素转换后的类型的占位符
实际上这个函数签名应该是
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
将函数行为参数化
-
map 和 CompactMap - 对元素进行变换
-
filter - 筛选
-
reduce - 将元素合并到一个总和的值
-
squence - 下一个元素
-
forEach - 对于一个元素执行怎样的操作
-
sort,lexicographicCompare 和 partition - 两个元素应该以怎样的顺序进行排列
-
first, firstIndex, contains - 元素是否符合某一个条件
-
elementsEqual和starts - 俩个元素是否相等
-
split - 这个元素是否是封割符
-
prefix - 判断为真,将元素过滤出来,一旦不为真,就将剩余结果抛弃,和filter类似,但是会提前退出。这个函数在处理无限序列或者延迟家孙(lazily-computed)的时候非常有用
-
drop - 判断为真,丢弃元素,一旦不为真,将剩余结果返回。和prefix(while:)相似,不过返回相反的集合
Filter
(1..<10).map { $0 * $0 }.filter { $0 % 2 == 0 } // [4, 16, 36, 64]
实现源码
extension Array {
func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
var result: [Element] = []
for x in self where isIncluded(x) {
result.append(x)
}
return result
}
}
对于判断条件filter的性能比contains低,因为contains不会因为计数而去全新创建一个数组,并且一旦找到第一个元素就会退出
Reduce
所有值合并为新值
let sum = fibs.reduce(0) { total, num in total + num } // 12
fibs.reduce(0, +) // 12
输入的值和输出的值可以不同
print(array.reduce("") { str, num in str + "\(num)," })
// 0,1,1,2,3,5
实现源码
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
}
}
Reduce实现map和filter
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 Each
对集合中每个元素调用一个函数,return不能返回到函数的作用域之外,他做的仅仅是从闭包中返回
array.forEach{
print($0)
if $0 > 2 {
return
}
print($0)
}
0
0
1
1
1
1
2
2
3
5
切片
通过下标获取元素
let slice = array[1...]
print(slice)
print(type(of: slice))
[1, 1, 2, 3, 5]
ArraySlice<Int>
切片类型ArraySlice<T>只是一种表示方式,它的背后仍然是原来的数组。因为数组的元素不会被复制,所以创建一个切片的代价是很小的。
[图片上传失败...(image-8e753c-1635170375028)]
ArraySlice和Array都满足了相同的协议(当中最重要的是Collection协议),所以可以把切片当数组处理
let newArray = Array(slice)
type(of: newArray) // Array<Int>
如果是切片用startIndex 和 endIndex 属性做索引计算。