轨道
/
Swift
Swift
/
练习
/
Double-Null0111:闭包永存
Double-Null0111:闭包永存

Double-Null0111:闭包永存

学习练习

简介

Swift 中的闭包是自包含的代码块,可以向它传入实参来触发计算并返回值。 闭包还会捕获所在环境中的值,并在计算中使用它们。 由于是自包含的,闭包可以像其他值一样在程序中传来传去,也可以赋给常量或变量。

闭包听起来很像 Swift 的函数,确实如此,而且事出有因。 Swift 中的函数是闭包的一种特例:函数必须有名字,而且定义时使用的语法略有不同。

严格来说,Swift 中的函数也是闭包,但当人们提到 Swift 里的“闭包”时,指的其实是闭包表达式。 闭包表达式的写法是:先写形参列表,接着是返回类型和关键字in,然后是闭包体,整体放在一对大括号里:

{ (a: Double, b: Double) -> Double in
  return (a + b) / 2.0
}

这定义了一个类型为(Double, Double) -> Double的闭包表达式,它的执行方式与这种类型的函数类似:向它传入相应类型的参数即可:

{ (a: Double, b: Double) -> Double in
  return (a + b) / 2.0
}(10.0, 15.0)
// returns 12.5

每次要执行时都写出完整的闭包表达式并不方便,所以闭包可以像 Swift 中的其他值一样赋给一个名字。 之后,只要把参数应用到这个名字上就能调用它:

let mean = { (a: Double, b: Double) -> Double in
  (a + b) / 2.0
}

mean(13.0, 100.0)
// returns 56.5

如上所示,闭包和普通函数一样,如果闭包体只有一个表达式,就可以省略return关键字。

闭包最常见的用法是与高阶函数配合:要么作为参数传入,要么作为结果返回:

[11, 75, 3, 99, 53].contains(where: { (x: Int) -> Bool in x > 100 })
// returns false

["apple", "ball", "carrot"].sorted(by: { (s1: String, s2: String) -> Bool in s1.count < s2.count })
// returns ["ball", "apple", "carrot"]

func makeAdder(base: Int) -> (Int) -> Int {
  { (x: Int) -> Int in base + x }
}

说明

又见面了,特工 Double-Null0111。

UMBRA 的势力又开始蠢蠢欲动了,这次要由你来挫败他们的最新阴谋。我们的情报部门告诉我们,那些手下正计划在总督的舞会上引爆一颗臭弹。

由于这些手下笨手笨脚,他们经常在实验室和办公室里不小心启动臭弹。又因为他们总是忘记如何解除臭弹,于是他们做了一个系统,用来根据炸弹的 ID 编号生成拆弹说明,列出剪断电线的顺序。

你的任务是编写代码,让你能在舞会上的臭弹爆炸之前拆除它。

1. 编写一个闭包来交换两根电线

每颗臭弹上都有三根颜色不同的电线。 编写一个闭包,它接收一个(String, String, String)元组,返回将前两个元素顺序交换后的元组。 把这个闭包赋值给名称flip。

需要注意的是,类型必须是ChangeClosure(或某种可发送类型),类型定义已在桩文件中给出。 否则代码将无法编译。

flip(("red", "yellow", "blue"))
// Returns ("yellow", "red", "blue")

2. 编写一个闭包来轮换电线

编写一个闭包,它接收一个(String, String, String)元组,返回将电线顺序向左轮换后的元组。 把这个闭包赋值给名称rotate。

需要注意的是,类型必须是ChangeClosure(或某种可发送类型),类型定义已在桩文件中给出。

rotate(("red", "yellow", "blue"))
// Returns ("yellow", "blue", "red")

3. 实现一个电线打乱生成器

为了弄清剪断臭弹电线的顺序,你需要实现一个函数,它根据臭弹的 ID 编号生成相应的打乱函数。

实现这个函数:

makeShuffle(
  flipper: @escaping ((String, String, String)) -> (String, String, String),
  rotator: @escaping ((String, String, String)) -> (String, String, String)
) -> ([UInt8], (String, String, String)) -> (String, String, String)

它接收一个交换两根电线的闭包和一个轮换三根电线的闭包作为输入,并返回一个闭包。 返回的这个闭包接收臭弹的 ID 编号和三根电线的顺序,然后计算电线需要剪断的顺序。 ID 编号由八个二进制位组成,每一位都会决定是交换还是轮换电线。

对于 ID 编号中的每一位,从最右边的位开始,如果该位是 0,就对电线元组应用flipper闭包;如果该位是 1,就应用rotator闭包,从而得到电线的新状态。 当 ID 的全部八位都应用了相应的闭包后,电线的最终状态就是需要剪断的顺序。

let shuffler = makeShuffle(flipper: flip, rotator: rotate)
// Returns ([UInt8], (String, String, String)) -> (String, String, String)
shuffler([1, 0, 0, 1, 1, 0, 1, 1], ("red", "yellow", "blue"))
// Returns ("red", "blue", "yellow")
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Swift Exercism

准备好开始 Double-Null0111:闭包永存 了吗?

注册 Exercism,借助 35 个概念116 个练习 和真人导师指导,学习并掌握 Swift,全部免费。