3.4 集合类型

在Swift语言中一共提供了3种集合类型:Array数组、Set集合和Dictionary字典。Array类型是一种有序集合,放入其中的数据都有一个编号,且编号从0开始依次递增。通过编号,开发者可以找到Array数组中对应的值。Set集合是一组无序的数据,其中存入的数据没有编号,开发者可以使用遍历的方法获取其中所有的数据。Dictionary集合是一种键值映射结构,其中每存入一个值都要对应一个特定的键,且键不能重复,开发者通过键可以直接获取到对应的值。Swift官方开发文档中的一张示例图片可以十分清晰地描述出这3种集合类型的异同,如图3-1所示。

图3-1 3种集合类型的异同

本节将介绍这3种集合类型的特点及操作数据的方法。

3.4.1 Array数组类型

虽然Array经常被称为数组,但是其中所能存放的元素并非只能是数字,可以存放任意类型的数据,但是所有数据的类型必须统一。实际开发中,Array中元素的类型决定了Array的类型,例如,一个存放整型数据的Array会被称为整型数组,一个存放字符串型数据的Array会被称为字符串型数组。在创建Array实例的时候,必须明确指定其中所存放元素的类型。使用Xcode开发工具创建一个命名为CollectType的playground,在其中编写如下示例代码:

        //Int型数组
        var array1:[Int]
        var array2:Array<Int>

上面两句代码都是声明了一个Int类型的数组实例,数组的创建可以使用两种方式,一种是使用Array的构造方法来创建,一种是使用中括号来快捷创建,示例如下:

        //创建空数组
        array1 = []
        array2 = Array()
        array1 = [1,2,3]
        array2 = Array(arrayLiteral: 1,2,3)

和String类型类似,空数组的含义并非是变量为nil,而是数组中的元素为空,Swift中只有Optional类型的变量可以为nil。

Swift语言中Array采用结构体来实现,对于大量重复元素的数组,开发者可以直接使用快捷方法来创建,示例如下:

        //创建大量相同元素的数组
        //创建有10个String类型元素的数组,并且每个元素都为字符串"Hello"
        var array3 = [String](repeating: "Hello", count: 10)
        //创建有10个Int类型元素的数组,且每个元素都为1
        var array4 = Array(repeating: 1, count: 10)

读者需要注意,数组在声明时,必须要明确其类型,但是开发者并不一定需要显式的指定类型,如果数组在声明时也设置了初始值,则编译器会根据赋值类型自动推断出数组的类型。Array数组中对加法运算符也进行了重载,开发者可以使用“+”进行两个数组的相加,相加的结果即将第2个数组中的元素拼接到第1个数组后面。需要注意,相加的数组类型必须相同,示例如下:

        //数组相加array5 = [1,2,3,4,5,6]
        var array5 = [1,2,3]+[4,5,6]

Array中提供了许多方法供开发者来获取数组实例的相关信息或者对数组进行增、删、改、查的操作。示例如下:

        var array = [1,2,3,4,5,6,7,8,9]
        //获取数组中元素的个数 9
        array.count
        //检查数组是否为空数组
        if array.isEmpty {
            print("array为空数组")
        }
        //通过下标获取数组中的元素 1
        var a = array[0]
        //获取区间元素组成的新数组 [1,2,3,4]
        var subArray = array[0...3]
        //获取数组的第1个元素
        var b = array.first
        //获取数组的最后一个元素
        var c = array.last
        //修改数组中某个位置的元素
        array[0] = 0
        //修改数组中区间范围的元素
        array[0...3] = [1,2,3,4]
        //向数组中追加一个元素
        array.append(10)
        //向数组中追加一组元素
        array.append(contentsOf: [11,12,13])
        //向数组中的某个位置插入一个元素
        array.insert(0, at: 0)
        //向数组中的某个位置插入一组元素
        array.insert(contentsOf: [-2, -1], at: 0)
        //移除数组中某个位置的元素
        array.remove(at: 1)
        //移除数组中首个位置的元素
        array.removeFirst()
        //移除最后一个位置的元素
        array.removeLast()
        //移除前几位元素,参数为要移除元素的个数
        array.removeFirst(2)
        //移除后几位元素,参数为要移除元素的个数
        array.removeLast(2)
        //移除一个范围内的元素
        array.removeSubrange(0...2)
        //替换一个范围内的元素
        array.replaceSubrange(0...2, with: [0,1])
        //移除所有元素
        array.removeAll()
        //判断数组中是否包含某个元素
        if array.contains(1){
            print(true)
        }

这里需要注意,只有当Array实例为变量时,才可以使用增、删、改等方法,常量数组不能进行与修改相关的操作。

开发者也可以使用for-in遍历来获取数组中的元素,示例如下:

        //Int型数组
        let arrayLet = [0,1,2,3,4]
        //(Int, Int)型数组
        let arrayLet2 = [(1,2), (2,3), (3,4)]
        //直接遍历数组
        for item in arrayLet {
            print(item)
        }
        //进行数组枚举遍历,将输出 (0,0) (1,1) (2,2) (3,3) (4,4)
        for item in arrayLet.enumerated(){
            print(item)
        }
        //进行数组角标遍历
        for index in arrayLet2.indices{
          print(arrayLet2[index], separator:"")
        }

可以直接对Array实例进行遍历,Swift中的for-in结构和Objective-C中的for-in结构还是有一些区别的,Swift中for-in结构在遍历数组时是会按照顺序进行遍历的。Array实例中还有一个enumerated()的方法,这个方法会返回一个元组集合,将数组的下标和对应元素返回。开发者也可以通过遍历数组的下标来获取数组中的元素,和String类型不同的是,Array中的下标可以是Int类型,而String中的下标是严格的Index类型,这里需要注意,不要混淆。

Array类型中有一个indices的属性,这个属性将返回一个Range范围,此范围即是数组下标的范围。

Array类型中还提供了一个排序函数,如果数组中的元素为整型数据,则可以使用系统提供的sort(isOrderedBefore:)方法来进行排序操作,如果是一些自定义的类型,开发者也可以对sort(isOrderedBefore:)方法传入闭包实现新的排序规则,这部分内容会在后面章节中详细介绍。进行数组排序的方法示例代码如下:

        var arraySort = [1,3,5,6,7]
        //从大到小排序
        arraySort = arraySort.sorted(isOrderedBefore: >)
        //从小到大排序
        arraySort = arraySort.sorted(isOrderedBefore: <)

下列方法可以获取数组中的最大值与最小值:

        var arraySort = [1,3,5,6,7]
        //获取数组中的最大值
        arraySort.max()
        //获取数组中的最小值
        arraySort.min()

3.4.2 Set集合类型

Set类型的集合不关注其中元素的顺序,但是其中的元素不可以重复,读者也可以将其理解为一个无序的集合。与Array一样,Set集合在进行声明时必须指定其类型,或者对其进行赋初值,使得编译器可以自行推断出Set的类型。声明与创建Set集合的示例代码如下:

        //创建set集合
        var set1:Set<Int> = [1,2,3,4]
        var set2 = Set(arrayLiteral: 1,2,3,4)

由于Set并不关注于其中元素的顺序,因此通过下标的方式来取值对Set集合来说并不十分有意义,但是Set类型依然是支持通过下标来获取其中元素的,示例如下:

        //获取集合首个元素(顺序不定)
        set1[set1.startIndex]
        //进行下标的移动
        //获取某个下标后一个元素
        set1[set1.index(after: set1.startIndex)]
        //获取某个下标后几位的元素
        set1[set1.index(set1.startIndex, offsetBy: 3)]

需要注意,Set的下标操作为不可逆的操作,只能向后移动,不能向前移动。

下面这个方法可以获取集合实例中的一些信息:

        //获取元素个数
        set1.count
        //判断集合是否为空集合
        if set1.isEmpty {
            print("集合为空")
        }
        //判断集合中是否包含某个元素
        if set1.contains(1){
            print("集合包含")
        }
        //获取集合中的最大值
        set1.max()
        //获取集合中的最小值
        set1.min()

Set集合变量同样也支持进行增、删、改、查的操作,示例如下:

        //向集合中插入一个元素
        set1.insert(5)
        //移除集合中的某个元素
        set1.remove(1)
        //移除集合中的第一个元素
        set1.removeFirst()
        //移除集合中某个位置的元素
        set1.remove(at: set1.index(of: 3)! )
        //移除集合中所有的元素
        set1.removeAll()

在使用remove(at:)方法删除集合某个位置的元素时,需要传入一个集合元素的下标值,通过Set实例的index(of:)方法可以获取具体某个元素的下标值,需要注意,这个方法将会返回一个Optional类型的可选值,因为要寻找的元素可能不存在,在使用时,开发者需要对其进行拆包操作。

Set集合类型与Array数组类型除了有序和无序的区别外,其还有一个独有的特点:Set集合可以进行数学运算,例如交集运算、并集运算、补集运算等。Swift官方开发文档中的一张图片示意了Set进行数学运算时的场景,如图3-2所示。

图3-2 集合进行数学运算示意图

从图3-2中可以看出,Set集合支持4类数学运算,分别为intersection(交集)运算、symmetricDifferenc(e交集的补集)运算、union(并集)运算和subtracting(补集)运算。intersection运算的结果为两个集合的交集,symmetricDifference运算的结果为a集合与b集合的并集除去a集合与b集合的交集,union运算的结果为两个集合的并集,subtracting运算的结果为a集合除去a集合与b集合的交集。上述4种运算的示例代码如下:

        var set3:Set<Int> = [1,2,3,4]
        var set4:Set<Int> = [1,2,5,6]
        //返回交集 {1,2}
        var setInter = set3.intersection(set4)
        //返回交集的补集{3,4,5,6}
        var setEx = set3.symmetricDifference(set4)
        //返回并集{1,2,3,4,5,6}
        var setUni = set3.union(set4)
        //返回第二个集合的补集{3,4}
        var setSub = set3.subtract(set4)

使用比较运算符“==”可以比较两个Set集合是否相等,当两个集合中的所有元素都相等时,两个集合才相等。Set中还提供了一些方法用于判断集合间的关系,示例代码如下:

        var set5:Set = [1,2]
        var set6:Set = [2,3]
        var set7:Set = [1,2,3]
        var set8:Set = [1,2,3]
        //判断是否是某个集合的子集,set5是set7的子集,返回ture
        set5.isSubset(of: set7)
        //判断是否是某个集合的超集,set7是set5的超集,返回ture
        set7.isSuperset(of: set5)
        //判断是否是某个集合的真子集,set5是set7的真子集,返回ture
        set5.isStrictSubset(of: set7)
        //判断是否是某个集合的真超集,set7不是set8的真超集,返回false
        set7.isStrictSuperset(of: set8)

与Array类似,Set也可以通过for-in遍历的方式来获取所有集合中的数据,可以通过3种方法来进行遍历:遍历元素、遍历集合的枚举与遍历集合下标。集合枚举会返回一个元组,元组中将集合下标和其对应的值一同返回,示例代码如下:

        //遍历元素
        for item in set7 {
            print(item)
        }
        //遍历集合的枚举
        for item in set7.enumerated() {
            print(item)
        }
        //遍历集合的下标
        for index in set7.indices {
            print(set7[index])
        }

集合虽然不强调元素顺序,但是在遍历时,开发者可以对其进行排序后再遍历,示例如下:

        //从大到小排序遍历集合
        for item  in set7.sorted(isOrderedBefore: >){
            print(item)
        }

3.4.3 Dictionary字典类型

字典是生活中常用的学习工具,字典在使用时是由一个索引找到一个结果。例如英汉词典,通过英文单词可以找到其对应的汉语解释;成语词典通过成语可以找到其对应的意义解释等。这种数据的存储模式被称为键值映射模式,即通过一个确定的键可以找到一个确定的值。类比上面的例子,在英汉词典中英文单词就是键,汉语释义就是值;在成语词典中,成语就是键,意义解释就是值。在Swift语言中也有这样的一种Dictionary集合,即字典集合类型。

Swift中的任何类型在进行声明时,都必须明确其类型,通过对Array和Set的学习你应该知道,对于集合类型,在声明时务必明确其内部元素的类型,字典也不例外,由于字典中的一个元素实际上是由键和值两个部分组成的,所以在声明字典时,也需要明确其键和值的类型。有两种方式可以进行字典的声明或创建,示例代码如下:

        //声明字典[param1:param2]这种结构用于表示字典类型,param1为键类型,param2为值类型
        var dic1:[Int:String]
        //这种方式和[:]效果一样,dic2与dic1为相同的类型
        var dic2:Dictionary<Int, String>
        //字典创建与赋值
        dic1 = [1:"1",2:"2",3:"3"]
        dic2 = Dictionary(dictionaryLiteral: (1, "1"), (2, "2"), (3, "3"))
        //在创建字典时,也可以不显示声明字典的类型,可以通过赋初值的方式来使编译器自动推断
        var dic3 = ["1":"one"]
        //创建空字典
        var dic4:[Int:Int] = [:]
        var dic5:Dictionary<Int, Int> = Dictionary()

需要注意,字典通过键来找到特定的值,在字典中值可以有重复,但是键必须唯一。这样才能保证一个确定的键能找到一个确定的值,并且如果开发者在字典中创建重复的键,编译器也会报出错误。

字典类型也支持使用isEmpty与count来判断是否为空并获取元素个数,示例代码如下:

        //获取字典中的元素个数
        dic1.count
        //判断字典是否为空
        if dic4.isEmpty{
            print("字典为空")
        }

通过具体键可以获取与修改对应的值,示例如下:

        //通过键操作值
        //获取值
        dic1[2]
        //修改值
        dic1[1]="0"
        //添加一对新的键值
        dic1[4] = "4"

上面代码中的dic1[1]=“0”与dic1[4]=“4”实际上是完成了相同的操作,可以这样理解:在对某个键进行赋值时,如果这个键存在,则会进行值的更新,如果这个键不存在,则会添加一对新的键值。然而在开发中,很多情况下需要对一个存在的键进行更新操作,如果这个键不存在,则不添加新键值对,要实现这种效果,可以使用Dictionary的更新键值方法,示例代码如下:

        //对键值进行更新
        dic1.updateValue("1", forKey: 1)

updateValue(value:forkey:)方法用于更新一个已经存在的键值对,其中第1个参数为新值,第2个参数为要更新的键。这个方法在执行时会返回一个Optional类型的值,如果字典中此键存在,则会更新成功,并将键的旧值包装成Optional值返回,如果此键不存在,则会返回nil。在开发中,常常使用if-let结构来处理,示例如下:

        //使用if let处理updateValue的返回值
        if let oldValue = dic1.updateValue("One", forKey: 1) {
            print("Old Value is \(oldValue)")
        }

其实在通过键来获取字典中的值时,也会返回一个Optional类型的值,如果键不存在,则此Optional值为nil,因此也可以使用if-let结构来保证程序的安全性,示例如下:

        //通过键获取的数据也将返回Optional类型的值,也可以使用if let
        if let value = dic2[1] {
            print("The Value is \(value)")
        }

下面方法可以实现对字典中键值对的删除操作:

        //通过键删除某个键值对
        dic1.removeValue(forKey: 1)
        //删除所有键值对
        dic1.removeAll()

在对字典进行遍历操作时,可以遍历字典中所有键组成的集合,也可以遍历字典中所有值组成的集合,通过Dictionary实例的keys属性与values属性分别可以获取字典的所有键与所有值,示例代码如下:

        //通过键来遍历字典
        for item in dic2.keys {
            print(item)
        }
        //通过值来遍历字典
        for item in dic2.values {
            print(item)
        }
        //直接遍历字典
        for item in dic2 {
            print(item)
        }
        for (key, value) in dic2 {
            print("\(key):\(value)")
        }

如上代码所示,也可以直接对字典实例进行遍历,遍历中会返回一个元组类型包装字典的键和值。

在进行字典键或者值遍历的时候,也支持对其进行排序遍历,实例如下:

        for item in dic2.keys.sorted(isOrderedBefore: >){
            print(dic2[item])
        }