首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Swift进阶五——集合类之Array

Swift进阶五——集合类之Array

作者头像
拉维
发布2020-12-29 15:12:29
发布2020-12-29 15:12:29
1.5K00
代码可运行
举报
文章被收录于专栏:iOS小生活iOS小生活
运行总次数:0
代码可运行

在Swift中创建数组的N种方式

代码语言:javascript
代码运行次数:0
运行
复制
let array = Array(repeating: 6, count: 3)
print(array) // [6, 6, 6]

使用区间初始化数组

代码语言:javascript
代码运行次数:0
运行
复制
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]

使用字符串初始化数组

代码语言:javascript
代码运行次数:0
运行
复制
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方法

代码语言:javascript
代码运行次数:0
运行
复制
let chars = Array("Norman Lee")
chars.forEach { (char) in
    print(char)
}

forEach方法无法使用break或者continue跳出或者跳过循环;使用break也只能退出当前一次循环的执行体

使用Iterator遍历数组

代码语言:javascript
代码运行次数:0
运行
复制
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来获取数组的索引区间:

代码语言:javascript
代码运行次数:0
运行
复制
let chars = Array("NormanLee")
for i in chars.indices {
    print(chars[i])
}

打印结果:
N
o
r
m
a
n
L
e
e

实际上,以上写法等同于如下写法:

代码语言:javascript
代码运行次数:0
运行
复制
let chars = Array("NormanLee")
for i in 0..<chars.count {
    print(chars[i])
}

也就是说,chars.indices等同于0..<chars.count。

数组中的查找操作

判断是否包含给定条件的元素

代码语言:javascript
代码运行次数:0
运行
复制
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" }

判断所有元素符合某个条件

代码语言:javascript
代码运行次数:0
运行
复制
// 只要有一个元素不符合条件,就会返回false,然后循环终止
let scores = [59, 66, 78, 82, 90, 95, 100]
scores.allSatisfy { (score) -> Bool in
    score >= 60
} // false
scores.allSatisfy({ $0 > 0}) // ture

查找元素(Optional)

代码语言:javascript
代码运行次数:0
运行
复制
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)

代码语言:javascript
代码运行次数:0
运行
复制
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)

查找最大最小元素

代码语言:javascript
代码运行次数:0
运行
复制
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:)利用给定的方式比较元素并返回数组中的最小元素

数组中元素的添加和删除操作

代码语言:javascript
代码运行次数:0
运行
复制
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

代码语言:javascript
代码运行次数:0
运行
复制
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

代码语言:javascript
代码运行次数:0
运行
复制
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

代码语言:javascript
代码运行次数:0
运行
复制
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进行强转:

代码语言:javascript
代码运行次数:0
运行
复制
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()会在原数组上将数组元素打乱,只能作用在数组变量上

代码语言:javascript
代码运行次数:0
运行
复制
var array = [Int](1...9)
array.shuffle()
print(array) // [4, 8, 2, 9, 1, 6, 5, 3, 7]

shuffled()会返回原数组的随机化数组,可以作用在数组变量和常量上。

代码语言:javascript
代码运行次数:0
运行
复制
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()会在原数组上将数组逆序,只能作用在数组变量上

代码语言:javascript
代码运行次数:0
运行
复制
var array = [Int](1...9)
array.reverse()
print(array) // [9, 8, 7, 6, 5, 4, 3, 2, 1]

reversed()会返回原数组的逆序“集合表示”,可以作用在数组变量和常量上,该方法不会分配新内存空间,其迭代器会以逆序的方式返回。

代码语言:javascript
代码运行次数:0
运行
复制
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)会将数组以某个条件分组,数组的前半部分都是不符合条件的元素,数组后半部分都是符合条件的元素

代码语言:javascript
代码运行次数:0
运行
复制
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()会在原数组上将元素排序,只能作用于数组变量。

代码语言:javascript
代码运行次数:0
运行
复制
var array = [10, 90, 20, 80, 30, 70, 40, 60, 55]
array.sort()
print(array)

sorted()会返回原数组的排序结果数组,可以作用在数组变量和常量上。

代码语言:javascript
代码运行次数:0
运行
复制
let array = [10, 90, 20, 80, 30, 70, 40, 60, 55]
let array2 = array.sorted()
print(array2)

交换数组两元素

swapAt会交换指定位置的两个元素

代码语言:javascript
代码运行次数:0
运行
复制
var array = [10, 90, 20, 80, 30, 70, 40, 60, 55]
array.swapAt(0, 3)
print(array)

数组的拼接操作

字符串数组拼接

joined()会拼接字符串数组里的所有元素为一个字符串

joined(separator: )会已给定的分隔符来拼接字符串数组里的所有元素为一个字符串

代码语言:javascript
代码运行次数:0
运行
复制
var array = ["Norman", "Lavie"]
print(array.joined()) // NormanLavie
print(array.joined(separator: ",")) // Norman,Lavie

元素为Sequence(序列)数组的拼接

joined()会拼接数组里的所有元素为一个更大的Sequence

joined(separator: )会已给定的分隔符来拼接数组里的所有元素为一个更大的Sequence。

代码语言:javascript
代码运行次数:0
运行
复制
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方法来创建迭代器

代码语言:javascript
代码运行次数:0
运行
复制
//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)的数据结构,仅限定在栈顶插入或者删除操作。栈结构的实际应用主要有数制转换、括号匹配、表达式求值等等。

代码语言:javascript
代码运行次数:0
运行
复制
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)

代码语言:javascript
代码运行次数:0
运行
复制
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])
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档