美文网首页
Swift 集合类型

Swift 集合类型

作者: 明若晴空 | 来源:发表于2021-02-23 11:58 被阅读0次

    Swift提供了三种主要的集合类型,即数组、集合和字典,用于存储值的集合。数组是值的有序集合。集合是唯一值的无序集合。字典是键值关联的无序集合。


    截屏2021-02-20 下午4.29.04.png

    Swift中的数组、集合和字典总是清楚地知道它们可以存储的值和键的类型。这意味着您不能将错误类型的值错误地插入到集合中。这也意味着您可以从集合中检索的值的类型明确的确保一致。

    注意
    Swift的数组、集合和字典类型,都被实现为泛型集合。有关泛型类型和集合的详细信息,请参见泛型

    集合的易变性

    如果您创建一个数组、一个集合或一个字典,并将其赋给一个变量,那么所创建的集合将是可变的。这意味着您可以在创建集合后通过添加、删除或更改集合中的元素来更改(或改变)集合。如果将数组、集合或字典赋给常量,则该集合是不可变的,并且其大小和内容不能更改。

    注意
    在不需要更改集合的所有情况下,都可以创建不可变的集合。这样做可以更容易地对代码进行推理,并使Swift编译器能够优化所创建集合的性能。

    数组

    数组在有序列表中存储相同类型的值。同一个值可以在数组中的不同位置多次出现。

    注意
    Swift的数组类型被桥接到Foundation的NSArray类。
    有关在Foundation和Cocoa中使用Array的更多信息,请参阅Array和NSArray之间的桥接

    数组类型快捷语法

    Swift 数组的类型完整地写为array<Element>,其中Element是允许数组存储的值的类型。您也可以将数组的类型以快捷形式写成[Element]。尽管这两种形式在功能上是相同的,但快捷形式是首选的,并且在本指南中提及数组类型时,都使用快捷形式。

    创建空数组

    可以使用初始化方法的语法创建特定类型的空数组:

    var someInts = [Int]()
    print("someInts is of type [Int] with \(someInts.count) items.")
    // Prints "someInts is of type [Int] with 0 items."
    

    注意,someInts 变量的类型从初始化方法的类型推断为[Int]类型。

    或者,如果上下文已提供类型信息,例如函数参数或已类型化的变量或常量,则可以使用空数组文字创建空数组,该文字写为[](一对空方括号):

    someInts.append(3)
    // someInts now contains 1 value of type Int
    someInts = []
    // someInts is now an empty array, but is still of type [Int]
    

    使用默认值创建数组

    Swift的数组类型还提供了一个用于创建一个特定大小的数组的初始化方法,并将其所有值设置为相同的默认值。向该初始值设定项传递适当类型的默认值(参数名为repeating):以及该值在新数组中重复的次数(参数名为count):

    var threeDoubles = Array(repeating: 0.0, count: 3)
    // threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
    

    通过将两个数组相加来创建数组

    通过使用加法运算符(+)将两个具有兼容类型的现有数组相加,可以创建一个新数组。新数组的类型是根据您添加到一起的两个数组的类型推断出来的:

    var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
    // anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]
    
    var sixDoubles = threeDoubles + anotherThreeDoubles
    // sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
    

    使用数组文本创建数组

    还可以使用数组文本初始化数组,这是写入一个或多个值来作为数组集合的一种简写方法。数组文本被写成一个值列表,用逗号分隔,用一对方括号括起来:


    截屏2021-02-20 下午4.33.56.png

    下面的示例创建一个名为shoppingList的数组来存储字符串值:

    var shoppingList: [String] = ["Eggs", "Milk"]
    // shoppingList has been initialized with two initial items
    

    shoppingList 变量声明为“字符串值的数组”,编写为[string]。因为这个特定数组指定了String的值类型,所以只允许存储String值。这里,shoppingList数组是用两个String值(“Eggs”和“Milk”)初始化的,这两个String值写在一个数组文本中。

    注意
    shoppingList数组被声明为变量(使用var声明)而不是常量(使用let声明),因为在下面的示例中,更多的元素被添加到shoppingList中。

    在本例中,数组文本包含两个字符串值,而不包含其他内容。这与shoppingList变量的声明类型(一个只能包含字符串值的数组)匹配,因此允许将数组文本的赋值作为用两个初始项来初始化shoppingList的一种方法。

    由于Swift的类型推断,如果使用包含相同类型值的数组文本初始化数组,则不必注写数组的类型。shoppingList的初始化可以用一个较短的格式编写

    var shoppingList = ["Eggs", "Milk"]
    

    因为数组文本中的所有值都是相同的类型,所以Swift可以推断[String]是shoppingList变量的正确类型。

    访问和修改数组

    可以通过数组的方法和属性,或者使用下标语法来访问和修改数组。
    要查找数组中的元素个数,请检查其只读类型的count属性:

    print("The shopping list contains \(shoppingList.count) items.")
    // Prints "The shopping list contains 2 items."
    

    使用Boolean类型的 isEmpty 属性作为检查count属性是否等于0的快捷方式:

    if shoppingList.isEmpty {
        print("The shopping list is empty.")
    } else {
        print("The shopping list isn't empty.")
    }
    // Prints "The shopping list isn't empty."
    
    

    您可以通过调用数组的append(_:)方法将新元素添加到数组末尾:

    shoppingList.append("Flour")
    // shoppingList now contains 3 items, and someone is making pancakes
    

    或者,使用加法赋值运算符(+=)追加一个或多个兼容项的数组:

    shoppingList += ["Baking Powder"]
    // shoppingList now contains 4 items
    shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
    // shoppingList now contains 7 items
    
    

    使用下标语法从数组中检索值,将要检索的值的索引立即传递到数组名称后的方括号中:

    var firstItem = shoppingList[0]
    // firstItem is equal to "Eggs"
    

    注意
    数组中的第一个元素的索引为0,而不是1。Swift中的数组总是从零索引开始的。

    可以使用下标语法更改给定索引处的现有值:

    shoppingList[0] = "Six eggs"
    // the first item in the list is now equal to "Six eggs" rather than "Eggs"
    

    使用下标语法时,指定的索引必须是有效的。例如,这样写shoppingList[shoppingList.count] = "Salt"来尝试将元素添加到数组末尾会导致运行时错误。
    也可以使用下标语法一次更改一串值,即使替换的值集合的长度与要替换的范围长度不同。以下示例将“Chocolate Spread”、“Cheese”和“Butter”替换为“Bananas”和“Apples”:

    shoppingList[4...6] = ["Bananas", "Apples"]
    // shoppingList now contains 6 items
    

    若要在指定索引处向数组中插入元素,请调用数组的insert(_:at:)方法:

    shoppingList.insert("Maple Syrup", at: 0)
    // shoppingList now contains 7 items
    // "Maple Syrup" is now the first item in the list
    

    这个对insert(_:at:)方法的调用在购物列表的最开始处插入一个值为“Maple glyph”的新元素,索引为0。

    类似地,可以使用remove(at:)方法从数组中移除项。此方法删除指定索引处的元素,并返回删除的元素(不过,如果不需要,可以忽略返回的值):

    let mapleSyrup = shoppingList.remove(at: 0)
    // the item that was at index 0 has just been removed
    // shoppingList now contains 6 items, and no Maple Syrup
    // the mapleSyrup constant is now equal to the removed "Maple Syrup" string
    

    注意
    如果尝试访问或修改超出数组现有边界的索引值,就会触发运行时错误。在使用索引之前,可以通过将其与数组的count属性进行比较来检查索引是否有效。数组中最大的有效索引是count-1,因为数组是从零开始索引的,但是当count为0(表示数组为空)时,没有有效索引。

    当移除某个元素时,数组中的任何缺口都会再闭合,因此索引0处的值再次等于“Six eggs”:

    firstItem = shoppingList[0]
    // firstItem is now equal to "Six eggs"
    

    如果要从数组中删除最后一项,请使用removeLast()方法而不是remove(at:)方法,以避免查询数组的count属性。与remove(at:)方法类似,removeLast()也会返回已删除的项:

    let apples = shoppingList.removeLast()
    // the last item in the array has just been removed
    // shoppingList now contains 5 items, and no apples
    // the apples constant is now equal to the removed "Apples" string
    

    迭代数组

    可以使用for-in循环迭代数组中的整个值的集合:

    for item in shoppingList {
        print(item)
    }
    // Six eggs
    // Milk
    // Flour
    // Baking Powder
    // Bananas
    

    如果需要每个元素的整数索引及其值,请改用enumerated()方法迭代数组。对于数组中的每个元素,enumerated()方法返回一个由整数和元素值组成的元组。整数从零开始,每一个元素都会对其加一;如果在整个数组中枚举,则这些整数与元素的索引匹配。作为迭代的一部分,可以将元组分解为临时常量或变量:

    for (index, value) in shoppingList.enumerated() {
        print("Item \(index + 1): \(value)")
    }
    // Item 1: Six eggs
    // Item 2: Milk
    // Item 3: Flour
    // Item 4: Baking Powder
    // Item 5: Bananas
    

    有关For in循环的更多信息,请参见For-in循环

    集合

    Set在集合中存储同一类型的不同值,但没有定义顺序。如果项的顺序不重要,或者需要确保元素只出现一次,则可以使用集合而不是数组。

    注意
    Swift的Set类型桥接到Foundation的NSSet类。
    有关在Foundation和Cocoa中使用Set的更多信息,请参阅Set和NSSet之间的桥接

    集合类型的哈希值

    一个类型必须是可散列(可以计算哈希值的),才能存储在一个集合中,也就是说,该类型必须提供一种为自己计算哈希值的方法。哈希值是一个Int值,对于所有相等的对象都是相同的,因此如果A==b,则A的哈希值等于b的哈希值。

    Swift的所有基本类型(比如String、Int、Double和Bool)在默认情况下都是可散列的,可以用作set的值类型或字典的key的类型。默认情况下,没有关联值的枚举case的值(如枚举中所述)也是可哈希的。

    注意
    您可以使用自己的自定义类型作为set的值类型或字典的key的类型,只要使自定义类型可以符合Swift标准库中的哈希协议。有关实现所需的hash(into:)方法的信息,请参阅Hashable。有关遵守协议的信息,请参阅协议

    Set的类型语法

    Swift的Set类型写为Set<Element>,其中Element是允许Set存储的类型。与数组不同,集合没有等价的快捷形式。

    创建和初始化空集

    可以使用初始化语法创建特定类型的空Set:

    var letters = Set<Character>()
    print("letters is of type Set<Character> with \(letters.count) items.")
    // Prints "letters is of type Set<Character> with 0 items."
    

    注意
    根据初始值设定项的类型,可以推断letters变量的类型为Set<Character>。

    或者,如果上下文已提供类型信息,例如函数参数或已类型化的变量或常量,则可以使用空数组文本创建空集:

    letters.insert("a")
    // letters now contains 1 value of type Character
    letters = []
    // letters is now an empty set, but is still of type Set<Character>
    

    使用数组文本创建Set

    您也可以使用数组文本来初始化集合,作为将一个或多个值写入Set集合的简写方式。
    下面的示例创建一个名为favoriteGenres的Set来存储字符串值:

    var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
    // favoriteGenres has been initialized with three initial items
    

    favoriteGenres变量声明为“一个字符串值类型的集合”,编写为Set<String>。因为这个特殊的集合指定了一个字符串的值类型,所以它只允许存储字符串值。在这里,favoritegeners集合用三个字符串值(“Rock”、“classic”和“Hip-hop”)初始化,这些字符串值写在一个数组文本中。

    注意
    favoriteGenres集声明为变量(使用var声明)而不是常量(使用let声明),因为在下面的示例中添加和删除了元素。

    如果Set类型不能从数组文本(包含不同类型的元素)推断出值类型,那就必须显式声明类型Set。但是,由于Swift的类型推断,如果使用仅包含一种类型的值的数组文本对集合进行初始化,则不必注写Set元素的类型。favoriteGenres的初始化可以用更短的格式编写:

    var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
    

    因为数组文本中的所有值都是相同的类型,Swift可以推断Set<String>是favoritegeners变量的正确类型。

    访问和修改集合

    可以通过Set的方法和属性来访问和修改Set。
    要查找Set中的项数,请检查其只读count属性:

    print("I have \(favoriteGenres.count) favorite music genres.")
    // Prints "I have 3 favorite music genres."
    

    使用Boolean类型的 isEmpty属性作为检查count属性是否等于0的快捷方式:

    if favoriteGenres.isEmpty {
        print("As far as music goes, I'm not picky.")
    } else {
        print("I have particular music preferences.")
    }
    // Prints "I have particular music preferences."
    
    

    您可以通过调用集合的insert(_:)方法将新项添加到集合中:

    favoriteGenres.insert("Jazz")
    // favoriteGenres now contains 4 items
    
    

    您可以通过调用集合的remove(_:)方法从集合中删除项,如果该项是集合的成员,则该方法将删除该项,并返回删除的值;如果集合不包含该项,则返回nil。或者,可以使用removeAll()方法删除集合中的所有项。

    if let removedGenre = favoriteGenres.remove("Rock") {
        print("\(removedGenre)? I'm over it.")
    } else {
        print("I never much cared for that.")
    }
    // Prints "Rock? I'm over it."
    

    若要检查集合是否包含特定项,请使用contains(_:)方法。

    if favoriteGenres.contains("Funk") {
        print("I get up on the good foot.")
    } else {
        print("It's too funky in here.")
    }
    // Prints "It's too funky in here."
    
    

    在集合上迭代

    可以使用for-in循环对集合中的值进行迭代。

    for genre in favoriteGenres {
        print("\(genre)")
    }
    // Classical
    // Jazz
    // Hip hop
    

    有关For in循环的更多信息,请参见For-in循环

    Swift的集合类型没有定义的顺序。要按特定顺序对集合的值进行迭代,请使用sorted()方法,该方法将集合的元素返回为使用<运算符排序的数组。

    for genre in favoriteGenres.sorted() {
        print("\(genre)")
    }
    // Classical
    // Hip hop
    // Jazz
    

    执行集合操作

    您可以有效地执行基本的集合操作,例如将两个集合组合在一起,确定两个集合有哪些共同的值,或者确定两个集合是否包含全部、部分或全部相同的值。

    基本集合运算

    下图描述了两个集合a和b,由阴影区域表示的各种集合运算的结果。


    截屏2021-02-20 下午4.47.46.png
    • 使用intersection(_:)方法创建一个新的集合,其中只包含两个集共用的值。
    • 使用symmetricDifference(_:)方法创建一个新的集合,其中的值在任一集合中,但不同时在两个集合中。
    • 使用union(_:)方法创建一个包含两个集合中所有值的新集合。
    • 使用subtracting(_:)方法创建一个值不在指定集合中的新集合。
    let oddDigits: Set = [1, 3, 5, 7, 9]
    let evenDigits: Set = [0, 2, 4, 6, 8]
    let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
    
    oddDigits.union(evenDigits).sorted()
    // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    oddDigits.intersection(evenDigits).sorted()
    // []
    oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
    // [1, 9]
    oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
    // [1, 2, 9]
    

    Set的成员和相等

    下图描述了三个集合a、b和c,其中重叠区域表示集合之间共享的元素。集合a是集合b的超集,因为a包含集合b中的所有元素。相反,集合b是集合a的子集,因为集合b中的所有元素也包含在a中。集合b和集合c彼此不相交,因为它们没有共同的元素。


    截屏2021-02-20 下午4.49.39.png
    • 使用相等运算符(==)确定两个集合是否包含所有相同的值。
    • 使用isSubset(of:)方法确定一个集合中的所有值是否被包含在指定的集合中。
    • 使用isSuperset(of:)方法确定一个集合是否包含指定集合中的所有值。
    • 使用isStrictSubset(of:)isStrictSuperset(of:)方法确定一个集合是指定集合的子集还是超集,但不等于指定集合。
    • 使用isDisjoint(with:)方法确定两个集合是否没有共同的值。
    let houseAnimals: Set = ["🐶", "🐱"]
    let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
    let cityAnimals: Set = ["🐦", "🐭"]
    
    houseAnimals.isSubset(of: farmAnimals)
    // true
    farmAnimals.isSuperset(of: houseAnimals)
    // true
    farmAnimals.isDisjoint(with: cityAnimals)
    // true
    

    字典

    在没有定义顺序的集合中,字典存储同一类型的键和同一类型的值之间的关联。每个值都与唯一键相关联,该键在字典中充当该值的标识符。与数组中的元素不同,字典中的元素没有指定的顺序。当需要根据值的标识符来查找值时,可以使用字典,这与使用真实世界的字典来查找特定单词的定义的方式非常相似。

    注意
    Swift的字典类型被桥接到Foundation的NSDictionary类。
    有关在Foundation和Cocoa中使用Dictionary的更多信息,请参阅Dictionary和NSDictionary之间的桥接

    字典类型的快捷语法

    Swift字典的类型完整地写为Dictionary<Key, Value>,其中Key是可以用作字典键的值的类型,Value是字典为这些键存储的值的类型。

    注意
    字典的Key类型必须符合哈希协议,就像Set中的值类型一样。

    您也可以用快捷形式将字典的类型写成[Key: Value]。尽管这两种形式在功能上是相同的,但快捷形式是首选的,在本指南中提及词典类型时使用快捷形式。

    创建空词典

    与数组一样,可以使用初始值设定项方法创建特定类型的空字典:

    var namesOfIntegers = [Int: String]()
    // namesOfIntegers is an empty [Int: String] dictionary
    

    本例创建一个类型为[Int: String]的空字典来存储人类可读的整数值名称。它的键是Int类型,值是String类型。

    如果上下文已经提供了类型信息,则可以使用空字典文本创建一个空字典,写为[:](一对方括号中的冒号):

    namesOfIntegers[16] = "sixteen"
    // namesOfIntegers now contains 1 key-value pair
    namesOfIntegers = [:]
    // namesOfIntegers is once again an empty dictionary of type [Int: String]
    

    使用字典文本创建字典

    您还可以使用字典文本初始化字典,它的语法与前面看到的数组文本类似。字典文本是将一个或多个键值对编写为dictionary集合的一种简写方法。

    键值对是键和值的组合。在字典文本中,每个键值对中的键和值用冒号分隔。键值对写为一个列表,用逗号分隔,用一对方括号括起来:


    截屏2021-02-20 下午4.55.16.png

    下面的示例创建了一个字典来存储国际机场的名称。在本字典中,键是三个字母的国际航空运输协会代码,值是机场名称:

    var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
    

    airports字典被声明为[String:String]类型,这意味着“其键属于String类型,其值也属于String类型的字典”。

    注意
    airports字典被声明为变量(使用var声明),而不是常量(使用let声明),因为在下面的示例中,更多的机场被添加到字典中。

    使用包含两个键值对的字典文本初始化字典airports。第一对键为“YYZ”,值为“Toronto Pearson”。第二对键为“DUB”,值为“Dublin”。

    此字典文本包含两个String: String对。此键值类型与airports变量声明的类型匹配(仅包含字符串键和字符串值的字典),因此允许将字典文本的赋值作为两个初始项,来初始化airports字典。

    与数组一样,如果要使用其键和值具有一致类型的字典文本进行初始化,则不必注写字典的类型。airports的初始化可以用较短的格式来代替:

    var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
    

    因为文本中的所有键都是一致的类型,同样地,所有值都是一致的类型,Swift可以推断[String:String]是用于字典的正确类型。

    访问和修改词典

    您可以通过字典的方法和属性、或使用下标语法来访问和修改字典。
    与数组一样,可以通过检查字典的只读属性count来确定字典中的项数:

    print("The airports dictionary contains \(airports.count) items.")
    // Prints "The airports dictionary contains 2 items."
    

    使用Boolean类型的isEmpty属性作为检查count属性是否等于0的快捷方式:

    if airports.isEmpty {
        print("The airports dictionary is empty.")
    } else {
        print("The airports dictionary isn't empty.")
    }
    // Prints "The airports dictionary isn't empty."
    

    可以使用下标语法将新项添加到词典中。使用适当类型的新键作为下标索引,并指定适当类型的新值:

    airports["LHR"] = "London"
    // the airports dictionary now contains 3 items
    

    您还可以使用下标语法更改与特定键关联的值:

    airports["LHR"] = "London Heathrow"
    // the value for "LHR" has been changed to "London Heathrow"
    

    作为下标语法的一种替代方法,我们可以使用字典的updateValue(_:forKey:)方法来设置或更新特定键的值。与上面的下标语法示例一样,updateValue(_:forKey:)方法为一个还不存在的键设置一个值,或者如果该键已经存在,则更新该值。但是,与下标不同的是,updateValue(_:forKey:)方法在执行更新后返回的是旧值。这使您能够检查是否发生了更新。

    updateValue(_:forKey:)方法返回的是字典的值类型的可选值。例如,对于存储String类型的值的字典来说,该方法返回的值类型为String?,或“可选String”。此可选值包含该键的旧值(如果更新之前存在的话),或者包含nil(如果不存在值的话):

    if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
        print("The old value for DUB was \(oldValue).")
    }
    // Prints "The old value for DUB was Dublin."
    

    还可以使用下标语法从字典中检索特定键的值。因为可能会请求不存在值的键,所以字典的下标会返回字典值类型的可选值。如果字典包含所请求键的值,则下标返回一个可选值,该值包含该键的现有值。否则,下标返回nil:

    if let airportName = airports["DUB"] {
        print("The name of the airport is \(airportName).")
    } else {
        print("That airport isn't in the airports dictionary.")
    }
    // Prints "The name of the airport is Dublin Airport."
    

    可以使用下标语法从字典中删除键值对,方法是为该键指定值nil:

    airports["APL"] = "Apple International"
    // "Apple International" isn't the real airport for APL, so delete it
    airports["APL"] = nil
    // APL has now been removed from the dictionary
    

    或者,使用removeValue(forKey:)方法从字典中删除键值对。如果存在该键值对,则此方法将其删除,并返回删除的值;如果不存在该键值对,则返回nil:

    if let removedValue = airports.removeValue(forKey: "DUB") {
        print("The removed airport's name is \(removedValue).")
    } else {
        print("The airports dictionary doesn't contain a value for DUB.")
    }
    // Prints "The removed airport's name is Dublin Airport."
    

    字典元素的迭代

    可以使用for-in循环迭代字典中的键值对。字典中的每个项都作为(key, value)元组返回,您可以将元组的成员分解为临时常量或变量,作为迭代的一部分:

    for (airportCode, airportName) in airports {
        print("\(airportCode): \(airportName)")
    }
    // LHR: London Heathrow
    // YYZ: Toronto Pearson
    

    有关For-In循环的更多信息,请参见For-In循环

    还可以通过访问字典的键和值属性来检索其键或值的可迭代的集合:

    for airportCode in airports.keys {
        print("Airport code: \(airportCode)")
    }
    // Airport code: LHR
    // Airport code: YYZ
    
    for airportName in airports.values {
        print("Airport name: \(airportName)")
    }
    // Airport name: London Heathrow
    // Airport name: Toronto Pearson
    

    如果需要用字典的键或值来使用创建Array实例的API,请使用keys或values属性初始化新数组:

    let airportCodes = [String](airports.keys)
    // airportCodes is ["LHR", "YYZ"]
    
    let airportNames = [String](airports.values)
    // airportNames is ["London Heathrow", "Toronto Pearson"]
    

    Swift的Dictionary类型没有定义的顺序。要按特定顺序迭代字典的键或值,请对其keys或values属性使用sorted()方法。

    相关文章

      网友评论

          本文标题:Swift 集合类型

          本文链接:https://www.haomeiwen.com/subject/jgjrfltx.html