在Swift中创建数组的N种方式
let array = Array(repeating: 6, count: 3)
print(array) // [6, 6, 6]
使用区间初始化数组
let numbers = [Int](1..<11)
print(numbers) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let numbers = Array(1...6)
print(numbers) // [1, 2, 3, 4, 5, 6]
使用字符串初始化数组
let chars = [String.Element]("Lavie")
print(chars) // ["L", "a", "v", "i", "e"]
let chars = Array("Norman")
print(chars) // ["N", "o", "r", "m", "a", "n"]
我们可以看到,chars的类型如下:
数组的遍历和索引
forEach方法
let chars = Array("Norman Lee")
chars.forEach { (char) in
print(char)
}
forEach方法无法使用break或者continue跳出或者跳过循环;使用break也只能退出当前一次循环的执行体。
使用Iterator遍历数组
let numbers = [Int](0...8)
var numberItrrator = numbers.makeIterator()
while let number = numberItrrator.next() {
print(number)
}
当迭代器有下一个元素的时候,就会执行一次while循环体。
数组的索引
startIndex返回第一个元素的位置,对于数组来说,永远都是0。
endIndex返回最后一个元素索引+1的位置,对于数组来说,等同步count。
如果数组为空,则startIndex等同于endIndex。
我们还可以通过indices来获取数组的索引区间:
let chars = Array("NormanLee")
for i in chars.indices {
print(chars[i])
}
打印结果:
N
o
r
m
a
n
L
e
e
实际上,以上写法等同于如下写法:
let chars = Array("NormanLee")
for i in 0..<chars.count {
print(chars[i])
}
也就是说,chars.indices等同于0..<chars.count。
数组中的查找操作
判断是否包含给定条件的元素
chars.contains("b")
chars.contains("N")
// contains(where predicate: (Element) throws -> Bool) rethrows -> Bool
// 数组中只要有一个元素符合要求,就会返回true
chars.contains { (char) -> Bool in
char == "e"
}
chars.contains { $0 == "c" }
判断所有元素符合某个条件
// 只要有一个元素不符合条件,就会返回false,然后循环终止
let scores = [59, 66, 78, 82, 90, 95, 100]
scores.allSatisfy { (score) -> Bool in
score >= 60
} // false
scores.allSatisfy({ $0 > 0}) // ture
查找元素(Optional)
let scores = [59, 66, 78, 82, 90, 95, 100]
scores.first // first返回数组中的第一个元素(optional),如果数组为空,则返回nil
scores.last // last返回数组中的最后一个元素(optional),如果数组为空,则返回last
scores.first { (score) -> Bool in
score >= 60
} // first(where)返回数组中第一个符合给定条件的元素(optional)
scores.last(where: { $0 < 60}) // last(where)返回数组中最后一个符合给定条件的元素(optional)
如果直接通过下标去取值,那么很可能会导致数组越界;使用first和last可以包容这种情形。
查找索引(Optional)
let scores = [59, 66, 78, 82, 90, 95, 100]
scores.firstIndex(of: 66) // Optional(1) firstIndex(of: )返回给定元素在数组中出现的第一个位置(Optional)
scores.firstIndex(where: {$0 > 80}) // Optional(3) firstIndex(where: )返回符合条件的第一个元素的位置(Optional)
scores.lastIndex(of: 88) // nil lastIndex(of: )返回给定元素在数组中出现的最后一个位置(Optional)
scores.lastIndex { (score) -> Bool in
score > 100
} // nil lastIndex(where: )返回符合条件的最后一个元素的位置(Optional)
查找最大最小元素
let scores = [59, 66, 78, 82, 90, 95, 100]
scores.max() // Optional(100) max()返回数组中最大的元素
scores.min() // Optional(59) min()返回数组中最小的元素
let students = [("lavie", 18), ("norman", 26), ("liwei", 19), ("lily", 23), ("bruce", 23), ("mike", 18)]
students.max { (student1, student2) -> Bool in
student1.1 < student2.1
} // max(by:)利用给定的方式比较元素并返回数组中的最大元素
students.min(by: {student1, student2 in student1.1 < student2.1}) // max(by:)利用给定的方式比较元素并返回数组中的最小元素
数组中元素的添加和删除操作
var array = [Int](0...6)
array.append(7) // 在末尾添加一个元素
array.append(contentsOf: (8..<20)) // 在末尾添加多个元素
array.insert(111, at: 0) // 在指定位置插入一个元素
array.insert(contentsOf: (1000...1003), at: 0) // 在指定位置插入多个元素
// 字符串也是一种集合,其元素类型是Character
var chars = [Character]()
chars.append(contentsOf: "abcde")
chars.insert(contentsOf: "fghijk", at: 0)
chars.remove(at: 0) // 移除并返回数组中的指定位置的元素
chars.removeFirst() // 移除并返回数组中的第一个元素
chars.removeLast() // 移除并返回数组中最后一个元素
chars.removeFirst(2) // 移除数组前面多个元素
chars.removeLast(2) // 移除数组后面多个元素
chars.removeSubrange((0...2)) // 移除数组中给定范围的元素
数组切片:ArraySlice
ArraySlice是数组或者其他ArraySlice的一段连续切片,和原数组共享内存。ArraySlice相对于Array,就类似于子字符串相对于原字符串的概念。
当要改变ArraySlice的时候,ArraySlice会copy出来,生成单独内存。ArraySlice拥有和Array基本完全类似的方法。
通过Drop来得到ArraySlice
dropFirst(:)“移除”原数组前面指定个数的元素得到一个ArraySlice
dropLast(:)“移除”原数组后面指定个数的元素得到一个ArraySlice
drop(:)“移除”原数组符合条件的元素得到一个ArraySlice
var scores = [66, 45, 78, 23, 67, 89, 12, 98, 6, 100]
scores.dropFirst()
scores.dropFirst(3)
scores.dropLast()
scores.dropLast(3)
scores.drop(while: {$0 < 60})
通过Prefix得到ArraySlice
prefix() 获取数组前面指定个数的元素组成的ArraySlice
prefix(upTo:)获取数组到指定位置(不包含指定位置)前面的元素组成的ArraySlice
prefix(through:)获取数组到指定位置(包含指定位置)前面的元素组成的ArraySlice
prefix(while:)获取数组前面符合条件的元素(到第一个不符合条件的元素截止)组成的ArraySlice
var scores = [66, 45, 78, 23, 67, 89, 12, 98, 6, 100]
scores.prefix(3)
scores.prefix(upTo: 3)
scores.prefix(through: 3)
scores.prefix(while: {$0 >= 60})
通过Suffix得到ArraySlice
suffix()获取数组后面指定个数的元素组成的ArraySlice
suffix(from:)获取数组从指定位置到结尾(包含指定位置)的元素组成的ArraySlice
var scores = [66, 45, 78, 23, 67, 89, 12, 98, 6, 100]
scores.suffix(3)
scores.suffix(from: 6)
通过Range得到ArraySlice
可以通过对数组下标指定Range来获取ArraySlice。可以使用闭区间、半开半闭区间、单侧区间,甚至可以直接使用...来获取整个数组组成的ArraySlice。
ArraySlice转成Array
需要注意的是,ArraySlice是无法直接赋值给一个Array的常量或者变量的:
如果想要将ArraySlice转成Array,我们需要使用Array进行强转:
var scores = [66, 45, 78, 23, 67, 89, 12, 98, 6, 100]
let aaa = scores[1...3]
scores = Array(aaa)
ArraySlice和原Array相互独立
虽然ArraySlice和Array是共享内存的,但是他们是相互独立的,也就是说,ArraySlice和Array它们添加删除元素都不会影响到对方。
数组的重排操作
数组元素的随机化
shuffle()会在原数组上将数组元素打乱,只能作用在数组变量上
var array = [Int](1...9)
array.shuffle()
print(array) // [4, 8, 2, 9, 1, 6, 5, 3, 7]
shuffled()会返回原数组的随机化数组,可以作用在数组变量和常量上。
let array = [Int](1...9)
let array2 = array.shuffled()
print(array) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(array2) // [5, 3, 9, 4, 7, 1, 2, 6, 8]
数组的逆序
reverse()会在原数组上将数组逆序,只能作用在数组变量上
var array = [Int](1...9)
array.reverse()
print(array) // [9, 8, 7, 6, 5, 4, 3, 2, 1]
reversed()会返回原数组的逆序“集合表示”,可以作用在数组变量和常量上,该方法不会分配新内存空间,其迭代器会以逆序的方式返回。
var array = [Int](1...9)
print(array) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
let array2 = array.reversed()
print(array2) // ReversedCollection<Array<Int>>(_base: [1, 2, 3, 4, 5, 6, 7, 8, 9])
⚠️注意,上例中的array和array2是共用内存的。
数组的分组
partition(by belongsInSecondPartition: (Element) throws -> Bool)会将数组以某个条件分组,数组的前半部分都是不符合条件的元素,数组后半部分都是符合条件的元素。
var array = [10, 90, 20, 80, 30, 70, 40, 60, 55]
let index = array.partition(by: { $0 >= 60 })
print(array) // [10, 55, 20, 40, 30, 70, 80, 60, 90]
print(array[0..<index]) // [10, 55, 20, 40, 30]
print(array[index...]) // [70, 80, 60, 90]
数组的排序
sort()会在原数组上将元素排序,只能作用于数组变量。
var array = [10, 90, 20, 80, 30, 70, 40, 60, 55]
array.sort()
print(array)
sorted()会返回原数组的排序结果数组,可以作用在数组变量和常量上。
let array = [10, 90, 20, 80, 30, 70, 40, 60, 55]
let array2 = array.sorted()
print(array2)
交换数组两元素
swapAt会交换指定位置的两个元素
var array = [10, 90, 20, 80, 30, 70, 40, 60, 55]
array.swapAt(0, 3)
print(array)
数组的拼接操作
字符串数组拼接
joined()会拼接字符串数组里的所有元素为一个字符串
joined(separator: )会已给定的分隔符来拼接字符串数组里的所有元素为一个字符串
var array = ["Norman", "Lavie"]
print(array.joined()) // NormanLavie
print(array.joined(separator: ",")) // Norman,Lavie
元素为Sequence(序列)数组的拼接
joined()会拼接数组里的所有元素为一个更大的Sequence
joined(separator: )会已给定的分隔符来拼接数组里的所有元素为一个更大的Sequence。
var numbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
let joined = numbers.joined(separator: [-1, -2])
print(Array(joined))
// [1, 2, 3, -1, -2, 4, 5, 6, -1, -2, 7, 8, 9]
数组的底层实现探究
首先来看一下数组的协议结构:
Array遵循RangeReplaceableCollection协议,RangeReplaceableCollection协议遵循Collection协议,Collection协议遵循Sequence协议。
接下来我们就来依次看一下Sequence、Collection和RangeReplaceableCollection到底是什么。
Sequence
Sequence,又称为序列,它代表的是一系列具有相同类型的值,你可以对这些值进行迭代。
序列是没有限定值的个数的,也就是说,序列中的值可以是有限个,也可以是无限个。
上图是Sequence协议的定义,我们可以看到,定义中有一个遵循IteratorProtocol协议的关联类型Iterator。
IteratorProtocol的定义如下:
Sequence会通过创建一个迭代器来提供对元素的访问。迭代器每次会产生序列中的一个值,当序列被耗尽的时候,next()应该返回nil。
定义自己的序列
按照如下思路去自定义Sequence:
1,首先自定义一个迭代器(需要遵循IteratorProtocol协议)
2,创建序列类型(遵循Sequence协议)
3,在序列中实现makeIterator方法来创建迭代器
//1,首先创建一个迭代器(需要遵循IteratorProtocol协议)
struct FibsIterator: IteratorProtocol {
let number: Int
var index: Int = 0
init(_ number: Int) {
self.number = number
}
var state = (0, 1)
//next方法用来生成下一个元素,并且管理序列中的值
mutating func next() -> Int? {
if index > number {
return nil
}
index += 1
let fibsNumber = state.0
state = (state.1, state.0 + state.1)
return fibsNumber
}
typealias Element = Int
}
//2,定义一个斐波那契数列
struct FibsSequence: Sequence {
let number: Int
init(_ number: Int) {
self.number = number
}
// 3,创建序列的迭代器
typealias Iterator = FibsIterator
func makeIterator() -> FibsSequence.Iterator {
return FibsIterator(10)
}
}
let fibs = FibsSequence(10)
for fib in fibs {
print(fib)
}
//打印如下:
0
1
1
2
3
5
8
13
21
34
55
Collection
Collection是Sequence的子协议,一个Collection是满足下列条件的Sequence:
1,必须是稳定的Sequence,也就是说,当多次被遍历的时候得到的结果保持一致。
2,除了可以线性遍历以外,集合中的元素也可以通过下标索引的方式被获取到。
3,和Sequence不同,Collection中的元素个数不能是无限的。
Array的迭代器
前面我们知道,Array首先是一个Sequence,因此它是有迭代器的,下面我们就来看看Array的迭代器是怎么做的。
根据上面的源码我们可以看到,Array的迭代器是IndexingIterator,它是一个索引化的迭代器。这个迭代器里面定义了next方法。
迭代器的创建方法makeIterator()是在Collection的一个扩展中实现的。需要注意的是,创建IndexingIterator的时候,是将self传递进去了。
因此,实际上next()方法中的_elements就是数组自身。
Array的下标访问
Array是通过subscript方法进行下标访问的,外界会向该方法中传入Int类型的索引值index。在subscript方法中会通过_getElement方法来获取到对应的元素。
在上图右侧,我们可以看到,Array的_getElement方法实现中,又调用了_buffer的getElement方法。
Array的Buffer
在Array的定义中,定义了两种类型的Buffer,一个是OC类型的,一个是Swift类型的,我们直接来看后者。
_ConfiguousArrayBuffer
_ConfiguousArrayBuffer的getElement
首先会判断传入的值是否是在数组的索引范围之内,接着会通过firstElementAddress获取到第一个元素的指针
UnsafeMutablePointer的下标操作
总结:
数组的Iterator实际上是一个IndexingIterator,在创建IndexingIterator的时候需要将self传入,因此数组迭代器的next方法是通过self的下标操作也就是Array的下标操作来进行的。
Array的下标操作最终会转到Buffer的getElement方法,Buffer的getElement方法又会转到UnsafeMutablePointer的下标操作,UnsafeMutablePointer的下标操作其实就已经是对指针进行操作了。
Array的endIndex和count
在Array中,endIndex是等于count的。但是放到Collection这个层面上,endIndex的类型是Self.Index,count的类型是Int,它们两者的类型不一定是一样的。
Collection的startIndex和endIndex的读取复杂度是O(1)的,而count的读取复杂度是O(n)。也就是说,如果可以使用startIndex和endIndex,那就尽可能使用startIndex和endIndex,当使用startIndex和endIndex实现不了的时候再使用count。
如何用数组来实现栈和队列
栈Stack
栈(Stack)是一种后入先出(Last In First Out)的数据结构,仅限定在栈顶插入或者删除操作。栈结构的实际应用主要有数制转换、括号匹配、表达式求值等等。
struct Stack<T> {
private var elements = [T]()
var count: Int {
return elements.count
}
var isEmpty: Bool {
return elements.isEmpty
}
var peek: T? {
return elements.last
}
// push在栈顶添加元素
mutating func push(_ element: T) {
elements.append(element)
}
// pop会从栈顶移除并返回元素
mutating func pop() -> T? {
return elements.popLast()
}
}
var stack = Stack<Int>()
stack.push(12)
stack.push(23)
stack.push(34)
print(stack) // Stack<Int>(elements: [12, 23, 34])
print(stack.pop()!) // 34
print(stack) // Stack<Int>(elements: [12, 23])
队列Queue
队列的特点是:先进先出(First In First Out)。
struct Queue<T> {
private var elements: [T] = []
var count: Int {
return elements.count
}
var isEmpty: Bool {
return elements.isEmpty
}
var peek: T? {
return elements.first
}
// enqueue入队
mutating func enqueue(_ element: T) {
elements.append(element)
}
// dequeue出队,移除最开始加进去的元素,也就是数组的第一个元素
mutating func dequeue() -> T? {
return isEmpty ? nil : elements.removeFirst()
}
}
var queue = Queue<Int>()
queue.enqueue(12)
queue.enqueue(23)
queue.enqueue(34)
print(queue) // Queue<Int>(elements: [12, 23, 34])
print(queue.dequeue()!) // 12
print(queue) // Queue<Int>(elements: [23, 34])