【Swift 学习笔记】内建集合类型

主要参考书:《Advanced Swift》- objc

内建集合类型

  • 数组

    • 数组是值语义

      • 意味着当为由let 声明的数组调用append 时,编译将会不通过;
      • 且把一个已经存在的数组赋值给另一个变量时,这个数组的内容将会被复制。不过好在Swift 标准库中的所有集合类型都采用“写时复制“「Copy-On-Write」技术,因此在新或旧变量被写入新值之前,二者在内存空间中共享唯一存储。
    • 数组的常见方法

      • 迭代数组 —— for x in array
      • 迭代除了第一个元素以外的数组其余部分 —— for x in array.dropFirst()
      • 迭代除了最后 5 个元素以外的数组 —— for x in array.dropLast(5)
      • 枚举数组中的元素和对应的下标 —— for (num, element) in collection.enumerated()
      • 寻找一个指定元素的位置 —— if let idx = array.index { someMatchingLogic($0) }
      • 对数组中的所有元素进行变形 —— array.map { someTransformation($0) }
      • 筛选出符合某个特定标准的元素 —— array.filter { someCriteria($0) }
      • 可见,Swift并不鼓励做索引计算,因为手动计算和使用索引将会带来很多潜在bug
      • 虽然无效的下表操作会造成可控的崩溃,但从内存安全的角度来看,下标操作是绝对安全的,因为标准库中的集合总是会执行边界检查,并禁止越界索引对内存的访问
    • 数组变形

      • Map

        // 求斐波那契数列前五项平方
        var fibs:[Int] = {0,1,1,2,3,5}
        
        // 遍历式
        var squared: [Int] = []
        for fib in fibs{
          squared.append(fib * fib)
        }
        squared //[0,1,1,4,9,25]
        
        // 函数式
        let squares = fibs.map{fib in fib * fib}
        squares //[0,1,1,4,9,25]
        
        // 二者完全等价
        // 但使用map 时,使用者有了更大的空间使得原本不得不被声明为var 的值改为let 声明
        
        // 实现
        extension Array{
          func map<T>(_ transform:(Element) -> T) -> [T]{
            var result: [T] = []
            result.reverserCapacity(count)
            for x in self{
              result.append(transform(x))
            }
            return result
          }
        }
        
      • 标准库中集合的其他常见方法

        // map 和 flatMap — 对元素进行变换。
        // filter — 只包含特定的元素。
        // allSatisfy — 针对一个条件测试所有元素。 
        // ★reduce — 将元素聚合成一个值。
        // forEach — 访问每个元素。
        // sort(by:), sorted(by:), lexicographicallyPrecedes(_:by:), 和 partition(by:) — 重排元素。
        // firstIndex(where:), lastIndex(where:), first(where:), last(where:), 和contains(where:) — 一个元素是否存在?
        // min(by:) 和 max(by:) — 找到所有元素中的最小或最大值。
        // elementsEqual(_:by:) 和 starts(with:by:) — 将元素与另一个数组进行比较。
        // split(whereSeparator:) — 把所有元素分成多个数组。
        // prefix(while:) — 从头取元素直到条件不成立。
        // drop(while:) — 当条件为真时,丢弃元素;一旦不为真,返回其余的元素 (和 prefix 类 似,不过返回相反的集合)。
        // removeAll(where:) — 删除所有符合条件的元素。
        // accumulate — 累加,和 reduce 类似,不过是将所有元素合并到一个数组中,并保留合并时每一步的值。
        // count(where:) — 计算满足条件的元素的个数 (它应该是 Swift 5 标准库的一部分,但 由于和 count 属性的名字冲突而延迟了,它可能会在随后的版本中被重新引入)。
        // indices(where:) — 返回一个包含满足某个条件的所有元素的索引列表,和 index(where:) 类似,但是不会在遇到首个元素时就停止。
        
      • filter:选出一个数组中满足条件的元素组成新数组

        // 寻找0 - 10 以内所有数平方中的偶数项
        (1..<10).map{$0 * $0}.filter{$0 % 2 == 0}
        
        // 实现
        extension Array{
          func filter(_ isIncluded:(Element) -> Bool -> [Element]){
            var result: [Element] = []
            for x in self where isIncluded(x){
              result.append(x)
            }
            return result
          }
        }
        
      • reduce:将数组中的所有元素合并为一个新的值

        let fibs = [0,1,1,2,3,5]
        
        let sum = fibs.reduce(0){total, num in total + num} // 12
        
        // 运算符本质上也是函数,因此上述表达式与下述表达式功能一致
        let sum = fibs.reduct(0, +)
        
        // reduce 的结果与运算过程不必拘泥于数据类型
        let sum = fibs.reduce(str, num in str + "\(nums),") // 得到一个字符串 "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
          }
        }
        
      • flatMap:一次性解决数组的提取和展平

        // 读取一个Markdown 文件,返回包含该文件中所有链接的URL 数组
        func extractLinks(markdownFile: String) -> [URL]
        
        let markdownFiles: [String]
        // 普通的通过map 实现,获得的是[[URL]]
        let nextedLinks = markdownFiles.map(extractLinks)
        // 将[[URL]] 展开,获得[URL]
        let links = nestedLinks.joined
        
        // 等价于直接的一个flatMap
        let links_0 = markdownFiles.flatMap(links)
        
        // 实现
        extension Array{
          func flatMap<T>(_ transform:(Element) -> [T]) -> [T]{
            var result: [T] = []
            for x in self{
              result.append(contentsOf: transform(x))
            }
            return result
          }
        }
        
        // 将不同数组里的元素进行合并
        let suits = ["♠", "♥", "♣", "♦"]
        let ranks = ["J","Q","K","A"]
        let result = suits.flatMap{suit in
        	ranks.map{rank in
            (suit, rank)
          }
        }/*
        [("♠", "J"), ("♠", "Q"), ("♠", "K"), ("♠", "A"), ("♥", "J"), ("♥","Q"), ("♥", "K"), ("♥", "A"), ("♣", "J"), ("♣", "Q"), ("♣", "K"), ("♣", "A"), ("♦", "J"), ("♦", "Q"), ("♦", "K"), ("♦", "A")]
        */
        
        
    • 数组迭代 - forEach

      for element in [1,2,3]{
        print(element)
      }
      
      [1,2,3].forEach{element in
      	print(element)
      }
      
      // for 循环和forEach 闭包并没有太大区别,但当需要在循环内对函数进行return 时,二者将有大不同
      (1..<10).forEach{number in
      	print(number)
      	if(number > 2) {return}		
      }
      // 该return 实际上并不会终止循环,它做的仅仅是从闭包中返回
      // 因此在forEach 的实现中会开始下一个循环的迭代
      
      • 当希望对数组中的每个元素调用某一函数时,通过结合forEach 传递函数名将使代码看上去更加简洁
    • 数组切片

      let slice = fibs[1..]		//[1,1,2,3,5]
      type(of: slice)		// ArraySlice<Int>
      
      • 切片类型只是数组的一种表示方式,背后的数据仍然是原来的数组,只不过用ArraySlice 的方式来表示。因此数组的元素不会被复制,所以创建一个切片的代价非常小。
      • ArraySlice 和Array 满足了相同的协议(最重要的是Collection协议),所以两者具有的方法一致,因此可以将切片当成数组来处理。
      • 如果需要将切片转为数组,可以通过将其传递给数组的构建方法:let new Array = Array(slice)
      • ★ 谨记:切片和背后的数组通过相同的索引来引用元素,因此切片索引不需要从0 开始。
  • 字典

    • 基本特性

      • 无序
      • 返回的总是Optional 值,当指定的键不存在时,返回nil。在数组中若使用越界下标将导致程序崩溃。
    • 基本方法

      enum Setting{
      	case text(String)
        case int(Int)
        case bool(Bool)
      }
      
      let defaultSettings: [String: Setting] = [
        "Airplane Mode": .bool(false)
        "Name": .text("My iPhone")
      ]
      
      • 移除:
        • 将对应键的值设为nil
        • removeValue() 将会返回被移除的值
      • 更新:
        • updateValue
    • 常见的其他方法

      • merge

        var settings = defaultSettings
        let overriddenSettings: [String: Setting] = ["Name": .text("Jane's iPhone")]
        settings.merge(overriddenSettings, uniquingKeysWith:{$1})
        settings //["Name": Setting.text("Jane\'s iPhone"), "Airplane Mode": Setting.bool(false)]
        //合并settings 和overrideenSettings,策略{$1}表示当某个键同时存在于settings 和overridenSettings 时,采用后者中的值
        
        
      • 从一个字典构建另一个字典

        // 对能保证键唯一性的使用Dictionary(uniqueKeysWithValues: )
        // 对无法保证的使用Dictionary
        extension Sequence where Element: Hashable{
          var frequencies: [Element: Int]{
            let frequencyPairs = self.map{($0, 1)}
            // 从Dictionary 构建Dictionary
            return Dictionary(frequencyPairs, uniquingKeysWith: +)
          }
        }
        
        let frequencies = "hello".frequencies		//["0":1, "h":1, "e":1, "l":2]
        frequencies.filter{$0.value > 1}		//["l": 2]
        
      • 对字典的值做映射

        let settingsAsStrings = settings.mapValues { setting -> String in switch setting {
        	case .text(let text): return text
        	case .int(let number): return String(number)
        	case .bool(let value): return String(value)
        	} 
        }
        settingsAsStrings // ["Name": "Jane\'s iPhone", "Airplane Mode": "false"]
        
    • Hashable

      • 对于结构体和枚举,只要它们是由可哈希的类型组成的,那么Swift 就可以帮我们自动合成Hashable 协议所需要的实现。
      • 如果不能利用自动Hashable 合成,那么需要首先让类型实现Equatable协议,然后可以实现 hash(into: ) 方法来满足Hashable 协议。
        • 该方法接受一个Hasher 类型参数,这个类型封装了一个通用的哈希函数,并在使用者向其提供数据时捕获哈希函数状态。
    • 应避免使用不具有值语义(如可变的对象)作为字典的键。当将一个对象用作字典键后,改变了它的内容,它的哈希值和/或相等特性往往也会改变。此时将无法在字典中再次找到它。此时字典会在错误的位置存储对象,这将导致字典内部存储的错误。

      • 而对于具有值语义的键,因为键不会和复制的值公用存储,因此它不会被从外部改变,因此不存在这个问题
  • 集合

    • 基本特性

      • 无序
      • 元素需实现 Hashable
      • 测试是否包含复杂度为常数
    • 常用方法

      let iPods: Set = ["iPod touch", "iPod nano", "iPod mini", "iPod shuffle", "iPod Classic"]
      let discontinuedIPods: Set = ["iPod mini", "iPod Classic", "iPod nano", "iPod shuffle"]
      
      // 补集
      let currentIPods = iPods.subtracting(discontinuedIPods) // ["iPod touch"]
      
      // 交集
      let touchscreen: Set = ["iPhone", "iPad", "iPod touch", "iPod nano"]
      let iPodsWithTouch = iPods.intersection(touchscreen) //["iPod nano", "iPod touch"]
      
      // 并集
      var discontinued: Set = ["iBook", "Powerbook", "Power Mac"] 
      discontinued.formUnion(discontinuedIPods)
      discontinued
      /*
      ["iPod shuffle", "iBook", "iPod Classic", "Powerbook", "iPod mini", "iPod nano", "Power Mac"]
      */
      
    • IndexSet:高效的索引存储集合

      • 对于连续的索引,仅需存储开头和结尾两个值,而不需要存储所有备选索引。因此,虽然功能上等价于Set<Int>,但实际运行中较之更为高效
    • CharacterSet:搞笑的存储Unicode 编码点的集合。

      • 经常被用来检查一个特定字符串是否只包含某个字符子集
    • 在闭包中使用集合

      // 筛选只出现过一次的元素
      extension Sequence where Element: Hashable{
        func unique() -> [Element]{
          var seen: Set<Element> = []
          return filter{element in
      			if seen.contais(element){
              return false
            }else{
              seen.insert(element)
              return true
            }
      		}
        }
      }
      
      [1,2,3,12,1,3,4,5,6,4,6].unique() //[1,2,3,12,4,5,6]
      
  • Range

    • a…b 表示两端闭

    • a…<b 表示左闭右开

    • a… 表示左闭右无穷

    • …b 表示左无穷右闭

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值