轨道
/
Swift
Swift
/
练习
/
诗歌俱乐部的第一条规则
诗歌俱乐部的第一条规则

诗歌俱乐部的第一条规则

学习练习

简介

导入

Swift 的标准库功能丰富,但通过导入外部库和框架,你还能用上许多额外的功能。 这些模块可能直接来自 Swift 项目(例如 Swift Argument Parser),来自 Apple 平台框架(例如 Foundation 和 Network Framework),也可能来自第三方开源库(例如 SwiftyBeaver)。

像 Foundation 这样的核心模块(Swift 中使用最广泛的库之一)随 Swift 工具链一起提供。 第三方库必须先添加到项目配置中(例如使用 Swift Package Manager),然后才能导入。

导入模块的方法是写下 import 关键字,后面跟上模块名。 比如,只要在程序中加上下面这行,就能导入 Foundation。

import Foundation

导入模块后,它的类型、方法和函数就可以在你的代码中使用了。 例如,导入 Foundation 会为 String 扩展出 components(separatedBy:) 等实用的工具方法。

按照惯例,import 语句放在 Swift 文件的顶部,这样任何人阅读代码时都能立刻看到依赖项。

字符串索引

String 中的单个字符可以用数组使用的那种下标方式访问。 但字符串使用的下标_不是_整数,不能直接拿来运算。 它们必须基于某个已知的下标计算出来,比如 startIndex,它指向非空字符串中第一个字符的位置,计算时要用 String 和 NSString 库提供的方法(导入 Foundation 模块后,Swift 中的字符串就能使用 NSString 的所有属性和方法)。

例如,给定下面这个字符串:

let csv = "apple,pear,peach,orange,cherry,lime,goosberry"

不能写 csv[21] 来取得 “orange” 中的 “g”,而必须计算出一个 String.Index 类型的值,再把这个下标传进去。 注意,这些下标本身并不是给人直接读取的。 它们被称为_不透明下标_,人类无需知道里面是什么。

let index = csv.index(csv.startIndex, offsetBy: 21)
csv[index]
// "g"
print(index)
// prints Index(_rawBits: 1376513)

但要注意,如果偏移量不是有效的下标,也就是说,如果它得到的位置在 startIndex 之前或 endIndex 之后,这个操作就会报错,导致程序崩溃。 要避免这个问题,可以指定一个限制下标。 这时返回的是一个可选下标,对于本来无效的下标会返回 nil。

let tooFar = csv.index(csv.startIndex, offsetBy: 200)
// error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
let tooFarButSafe = csv.index(csv.startIndex, offsetBy: 200, limitedBy: csv.endIndex)
// nil

此外,下标不能在字符串之间共享。 例如,用上面算出的 “orange” 中 “g” 的 index 去索引字符串 fam = "This is my family: 👨‍👩‍👦‍👦, this is our dog: 🐶, this is our cat: 🐱!",也就是 fam[index],会让程序崩溃。

造成这一切的原因有很多,但归根结底是 “Unicode 很棘手”。

一般来说,如果你需要随机访问单个字符,最好改用别的数据结构,比如 Array<Character>。

一些实用的方法

Swift 提供了 first 和 last 方法,用来获取字符串的第一个和最后一个字符。 这两个方法返回可选字符,所以如果字符串为空,会返回 nil。

let str = "Hello, world!"
print(str.first) // Prints Optional("H")
print(str.last)  // Prints Optional("!")

你也可以用 prefix 和 suffix 方法获取字符串的子串。 这两个方法接收一个整数作为实参,返回包含字符串前 n 个或后 n 个字符的子串。

let str = "Hello, world!"
print(str.prefix(5)) // Prints "Hello"
print(str.suffix(6)) // Prints "world!"

字符串方法

处理文本是编程中常见的任务。 Swift 以及 Foundation 框架提供了许多方便的方法和属性,用来检查、转换和操作字符串。

使用 components(separatedBy:) 拆分字符串

要根据分隔符把字符串拆分成子串数组,请使用 components(separatedBy:)(需要导入 Foundation):

import Foundation

let sentence = "Hello, world!"
let words = sentence.components(separatedBy: " ")
print(words) // Prints ["Hello,", "world!"]

使用 hasPrefix(_:) 和 hasSuffix(_:) 检查前缀和后缀

要检查字符串是否以特定的字符序列开头或结尾,请使用 hasPrefix(_:) 和 hasSuffix(_:):

let greeting = "Hello, world!"
print(greeting.hasPrefix("Hello"))  // Prints true
print(greeting.hasSuffix("world!")) // Prints true

更改大小写

你可以用下面这些标准方法和属性来转换字符串的大小写:

  • lowercased():返回字符串的小写副本。
  • uppercased():返回字符串的大写副本。
  • capitalized:返回每个单词首字母大写的副本(来自 Foundation)。
let greeting = "hello, world!"
print(greeting.lowercased())  // Prints "hello, world!"
print(greeting.uppercased())  // Prints "HELLO, WORLD!"
print(greeting.capitalized)   // Prints "Hello, World!"

使用 replacingOccurrences(of:with:) 替换子串

要把目标子串的所有出现都替换成另一个字符串,请使用 replacingOccurrences(of:with:)(来自 Foundation):

let greeting = "Hello, world!"
let updated = greeting.replacingOccurrences(of: "world", with: "Swift")
print(updated) // Prints "Hello, Swift!"

使用 joined(separator:) 连接字符串

joined(separator:) 方法属于字符串集合,例如 [String]。 它用一个分隔符把各个元素连接成单个字符串:

let words = ["Hello", "world"]
let sentence = words.joined(separator: ", ")
print(sentence) // Prints "Hello, world"

说明

镇上新开了一家诗歌俱乐部,你正想着要不要去。 因为以前出过一些事,俱乐部有一套非常特别的进门规定,你得先掌握它,才能尝试入场。

诗歌俱乐部有两扇门,都有人把守。 想进去,你得破解出当天的密码:

1. 把字符串拆分成单独的行

你要做的第一件事,是把这首诗拆分成单独的行。 守卫会念出一首诗,你要把它拆成若干行。

实现函数splitOnNewlines,它接收一个字符串,以换行符为分隔符,把它拆分成一个字符串数组。 函数应返回一个字符串数组,其中每个字符串都是诗中的一行。

splitOnNewlines("Hello.\nHow are you?\n\nI'm doing fine.")
// returns ["Hello." ,"How are you?", "", "I'm doing fine"].

2. 前门

  1. 守卫会念出一首诗
  2. 你要把这首诗拆分成单独的行。
  3. 你要取出每一行的首字母。
    • 如果这一行是空的,就用_代替。
  4. 你要把这些字母拼起来,组成一个单词。

比如,他们最喜欢的作家之一是 Michael Lockwood,他写过下面这首_藏头诗_,也就是说每句的首字母能拼成一个单词:

Stands so high
Huge hooves too
Impatiently waits for
Reins and harness
Eager to leave

守卫念出这首诗时,你要把它拆分成单独的行,然后回答每一行的首字母,也就是["S", "H", "I", "R", "E"]。

然后守卫会把你回答的字母数组所拼成的单词给你,让你把它变成首字母大写的单词形式。 最后你返回的密码是"SHIRE",你就能进去了。

实现函数frontDoorPassword,它接收一个表示这首诗的字符串。 函数应返回你要告诉守卫的密码。

frontDoorPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nReins and harness\nEager to leave")
// returns "SHIRE"

3. 后门

在俱乐部后面,你能见到最著名的诗人,那里就像 VIP 专区。 因为不是谁都能进,后门的流程要绕一些。

  1. 守卫会念出一首诗。
  2. 你要把这首诗拆成若干行。
  3. 你要取出每一行的末字母。
    • 有的行末尾会带空格,所以你需要把它们去掉。
  4. 你要把这些字母拼起来,然后在拼成的单词末尾加上", please"。

比如,前面提到的那首诗也是_藏尾诗_,也就是说每句的末字母能拼成一个单词:

Stands so high
Huge hooves too
Impatiently waits for
Reins and harness
Eager to leave

守卫念出这首诗时,你要把它拆分成单独的行,去掉末尾的空格,然后回答每一行的首字母,也就是["h", "o", "r", "s", "e"]。

然后守卫会把你回答的字母数组所拼成的单词给你,让你把它变成首字母大写的单词形式,并加上", please"。 最后你返回的密码是"horse, please",你就能进去了。

实现函数backDoorPassword,它接收一个表示这首诗的字符串。 函数应返回你要告诉守卫的密码。

backDoorPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nReins and harness\nEager to leave")
// returns "horse, please"

4. 密室

俱乐部后室里还有一扇门,通向密室,只有最顶尖的诗人才能进入。

  1. 守卫会念出一首诗。
  2. 你要把这首诗拆成若干行。
  3. 你要取出第 ith 行的第 ith 个字母。
  4. 你要把这些字母拼起来,然后在拼成的单词末尾加上"!"。
  5. 你要把这个单词变成大写形式。

比如,前面提到的那首诗稍作修改后,就符合这个规律:

Stands so high
Huge hooves too
Impatiently waits for
Rider with harness
Eager to leave

守卫念出这首诗时,你要把它拆分成单独的行,去掉末尾的空格,然后回答每一行的第 ith 个字母,也就是 ["S", "u", "p", "e", "r"]。

然后守卫会把你回答的字母数组所拼成的单词给你,让你把它变成大写形式,并加上"!"。 最后你返回的密码是SUPER!,你就能进去了。

实现函数secretRoomPassword,它接收一个表示这首诗的字符串。 函数应返回你要告诉守卫的密码。

secretRoomPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nRider with harness\nEager to leave")
// returns "SUPER!"
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Swift Exercism

准备好开始 诗歌俱乐部的第一条规则 了吗?

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