集合类型(二)

集合类型模块分四篇笔记来学习:

  • 第一篇:
  • 数组和可变性
  • 数组的变形
  • 第二篇:
    • 字典和集合
    • 集合协议
  • 第三篇:
    • 集合
  • 第四篇:
    • 索引

本篇开始学习第二篇,Go!

1.字典和集合

Swift 中另一个关键的数据结构是字典。字典指的是包含键以及它们所对应的值的数据结构。在一个字典中,每个键都只能出现一次。
举个例子,我们现在来为设置界面构建一些基础代码。我们先定义了 Settings 协议,遵守这个协议的类型需要提供一个能够设定它的 UIView 对象。比如对于 String,我们返回 UITextField,而对于布尔值,我们返回 UISwitch:

protocol Setting {
  func settingsView() -> UIView
}

现在我们可以定义一个由键值对组成的字典了。这个字典中的键是设置项的名字,而值就是这些设定所存储的值。我们使用 let 来定义字典,也就是说,我们之后不会再去更改它。

let defaultSettings: [String:Setting] = [
  "Airplane Mode": true,
  "Name": "My iPhone",
]

变更

和数组一样,使用 let 定义的字典是不可变的:你不能向其中添加或者修改条目。如果想要定义一个可变的字典,你需要使用 var 进行声明。想要将某个值从字典中移除,可以将它设为 nil。

有用的字典扩展

我们可以自己为字典添加一些扩展,比如增加将两个字典合并的功能。

举例来说,当我们将设置展示给用户时,我们希望将它们与用户存储的设置进行合并后再一起展示。假设我们已经有一个 storedSettings 方法来读取用户所存储的设置:

现在,我们来为字典增加一个 merge 函数。字典遵守 SequenceType 协议,为了通用性,我们可以让这个方法接受的参数是任意的遵守 SequenceType 协议、并且能产生 (Key,Value) 键值对的类型。这样一来,我们将不仅能够合并字典类型,也能对其他类似的类型进行相同的操作。

extension Dictionary {
  mutating func merge<S: SequenceType
  where S.Generator.Element == (Key,Value)>(other: S) {
    for (k, v) in other {
      self[k] = v
    }
  }
}

现在我们可以将存储的设置与默认设置进行合并了:

var settings = defaultSettings
settings.merge(storedSettings())

另一个有意思的扩展是从一个 (Key, Value) 键值对的序列来创建字典。我们可以先创建一个空字典,然后将序列合并到字典中去。这样一来,我们就可以重用上面的 merge 方法,让它来做实际的工作:

extension Dictionary {
  init<S: SequenceType where S.Generator.Element == (Key,Value)>(_ sequence: S) {
    self = [:]
    self.merge(sequence)
  }
}

// 所有 alert 默认都是关闭的
let defaultAlarms = (1..<5).map { ("Alarm \($0)", false) }
let alarmsDictionary = Dictionary(defaultAlarms)

在闭包中使用集合

在 Swift 标准库中,还包含了 Set 类型。Set 是一系列元素的无序集合,每个元素在 Set 中只会出现一次。Set 遵循 ArrayLiteralConvertible 协议,也就是说,我们可以通过使用和数组一样的字面量语法来初始化一个集合。

let mySet: Set<Int> = [1, 2]

在你的函数中,无论你是否将它们暴露给调用者,字典和集合都会是非常有用的数据结构。举个例子,如果我们想要为 SequenceType 写一个扩展,来获取序列中所有的唯一元素,我们只需要将这些元素放到一个 Set 里,然后返回这个集合的内容就行了。不过,因为 Set 并没有定义顺序,所以这么做是不稳定的,输入的元素的顺序在结果中可能会不一致。为了解决这个问题,我们可以创建一个扩展来解决这个问题,在扩展方法内部我们还是使用 Set 来验证唯一性:

  extension SequenceType where Generator.Element: Hashable {
    func unique() -> [Generator.Element] {
      var seen: Set<Generator.Element> = []
      return filter {
          if seen.contains($0) {
            return false
          } else {
            seen.insert($0)
            return true
          }
      }
    }
  }

上面这个方法让我们可以找到序列中的所有不重复的元素,并且维持它们原来的顺序。

2.集合协议

留心看看数组、字典以及集合的定义,我们会发现它们都遵守 CollectionType 协议。更进一步,CollectionType 本身是一个遵守 SequenceType 的协议。而序列 (sequence) 使用一个 GeneratorType 来提供它其中的元素。简单说,生成器 (generator) 知道如何产生新的值,序列知道如何创建一个生成器,而集合 (collection) 为序列添加了有意义的随机存取的方式。下面开始逆着学习。

生成器

GeneratorType 协议通过两部分来定义。

  1. 它要求遵守它的类型都需要有一个 Element 关联类型,这个类型也就是生成器产生的值的类型。(在数组中,生成器的元素类型就是数组的元素类型。在字典中,元素类型是一个同时包含键和值的多元组,也就是 (Key, Value))
  2. GeneratorType 定义的第二部分是 next 函数,它返回类型为 Element 的可选值。对于遵守 GeneratorType 的值,你可以一直调用它的 next 函数,直到你得到 nil 值。

结合这两部分,我们就能得到 GeneratorType 协议的定义了:

  protocol GeneratorType {
      associatedtype Element
      mutating func next() -> Element?
  }

你只能将生成器的值循环一次。也就是说,生成器并没有值语义。所以我们将会使用 class 来创建生成器,而不使用 struct

我们能写出的最简单的生成器是一个在被询问下一个值时每次都返回常数的生成器:

class ConstantGenerator: GeneratorType {
    typealias Element = Int
    func next() -> Element? {
        return 1
    }
}

或者采用隐式的赋值:

class ConstantGenerator: GeneratorType {
  func next() -> Int? {
    return 1
  }
}

声明一个斐波那契数的生成者:

class FibsGenerator: GeneratorType {
    var state = (0, 1)
    func next() -> Int? {
      let upcomingNumber = state.0
      state = (state.1, state.0 + state.1)
      return upcomingNumber
    }
}

上面这些生成器都能且只能被迭代一次。如果我们想要再次进行迭代,那么就需要创建一个新的生成器。这也通过它的定义有所暗示:我们将它们声明成了类而非结构体。在被不同变量共享的时候,只有引用的传递,而不发生复制。

Swift 标准库中的 AnyGenerator 有着曲折的过去。Swift 最开始的时候曾经有个叫做 GeneratorOf 的类型,它是一个结构体,做着与现在 AnyGenerator 类似的事情。在 Swift 2.0 的时候,AnyGenerator 取代了 GeneratorOf,并且它是一个类。不过在 Swift 2.2 里,它又变成了一个结构体。虽然 AnyGenerator 现在是一个结构体,但是它并不具有值语义,因为它其实将生成器的实现封装到了一个方法的引用类型中去。作为存在状态的结构,生成器其实在 Swift 标准库中显得有那么一点格格不入,因为标准库里绝大部分类型都是具有值语义的结构体。

避免去复制生成器。当你需要生成器的时候,总是去创建一个新的。我们接下来要说的序列类型 (SequenceType) 就很好地遵守了这一原则。

序列

进行多次迭代是很常见的行为,因此 SequenceType 应运而生。SequenceType 是构建在 GeneratorType 基础上的一个协议,它需要我们指定一个特定类型的生成器,并且提供一个方法来创建新的生成器:

protocol SequenceType {
  associatedtype Generator: GeneratorType
  func generate() -> Generator
}

举个例子,想要多次枚举前面我们提到的前缀的话,我们可以把 PrefixGenerator 放到一个序列中去。我们在这里没有显式地指定 GeneratorType 的类型,通过读取 generate 方法的返回类型,编译器可以为我们推断出所需要的 GeneratorType:

struct PrefixSequence: SequenceType {
    let string: String
    func generate() -> PrefixGenerator {
        return PrefixGenerator(string: string)
    }
}

因为我们实现了 SequenceType 协议,我们现在已经可以用 for 循环来迭代给定字符串的所有前缀了:

for prefix in PrefixSequence(string: "Hello") {
  print(prefix)
}

for 循环的工作原理是这样的:编译器为序列创建一个新的生成器,然后一直调用这个生成器的 next 方法获取其中的值,直到它返回 nil。从本质上说,for 就是下面这段代码的一种简写:

var generator = PrefixSequence(string: "Hello").generate()
while let prefix = generator.next() {
  print(prefix)
}

还有一种更简单的方法来创建生成器和序列。相比于创建一个新的类,我们可以使用 Swift 内建的 AnyGenerator 和 AnySequence 类型。它们接受一个函数作为参数,并根据这个函数为我们构建相应的类型。示例如下:

func fibGenerator() -> AnyGenerator<Int> {
  var state = (0, 1)
  return AnyGenerator {
      let result = state.0
      state = (state.1, state.0 + state.1)
      return result
  }
}

通过生成器创建序列的更加容易:

let fibSequence = AnySequence(fibGenerator)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,793评论 1 10
  • 包含内容 maven属性 构建环境的差异 资源过滤 Maven Profile Web资源过滤 在profile中...
    zlcook阅读 1,096评论 0 7
  • 如果没有孩子,我无法想象今天自己的模样。因为在有孩子之前,我从来不知道怕,也不太会忍耐,我的人生就是快意恩...
    悠然_3c09阅读 177评论 0 2
  • 第一眼的你 美OR丑 善OR恶 第一眼的你 真真假假 假假真真 第一眼你看到的是什么 第一眼在向你诉说什么 第一眼...
    刘赟阅读 216评论 0 0