怎么找到英文字符串中的最后一个词
2024/12/12
注:本文由题目《输出最后一个词的长度》拓展而来,以下思路可能稍显怪异
设计一个程序,输入英文字符串,输出其中最后一个词。
先理解题目。我们要找到一个东西,需要抓住它的特点,英文中的“词”具有显而易见的下面几个特点:
- 仅由英文字母构成
- 词与词之间用空格或标点分隔
针对第一个特点,了解 ASCII 码特性的我们可以构想出以下函数:
// 检查字符是否属于英文字母
inline const bool is_eng_char(const char &c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
而第二个特点就稍微显得有些棘手,我们可以立刻想象到以下情形:
Hello, World!
^ ^ ^ ^ ^ ^
---- ---------- ---------------------
如果遇到了多个空格,我们需要分别依次跳过它们。于是,我们很自然地想到了以下循环:
const string find_last_word(const string &str) {
// ...
// 从尾到头遍历字符串
for (int i = str.length() - 1; i >= 0; i--) {
// 找到最后一个词,开始计数
// ...
// 最后一个词结束,跳出循环
}
// return ...
}
用怎么样的数据结构来表示最后一个词呢?我相信很多人(包括我)会立即想到:
struct Word {
int left;
int right;
}
这本是很直观的,但我们对极端情况稍加思考,就能发现以上数据结构的局限性,试想以下情形:
// (一个空的字符串!)
Word word = {0, 0};
在正常情况下:
// The correct answer is A!
Word word = {22, 22};
对于一个空的字符串,其对其取下标零显然是不可能的,这种数据结构的局限性在于,其只能表示至少有一位的词,无法表示一个零长词。 因此,我们面临两种选择:
- 继续使用该数据结构,对零长词进行特殊处理
- 设计另外一种能够自然表示出零长词的数据结构
显然第二种是更加令人舒适的做法,但我们可能一时很难想到。这时我就不得不提醒诸位:
孔子曰:学而不思则罔,思而不学则殆
翻开 C++标准库,看到string类的substr方法,你是否能够想到:
struct Word {
int pos;
int len;
}
或许曾经你在使用substr的时候并不理解它为什么要如此设计,但我相信现在你已经懂了!
接下来让我们来完成这个循环:
int len = 0, pos = 0;
for (int i = str.length() - 1; i >= 0; i--) {
if (is_eng_char(str[i])) {
len++;
} else {
if (len > 0) {
pos = i + 1;
break;
}
}
}
然而,还差一点才能达到完美,我们知道,C++中string索引的类型为size_t,而size_t又是unsigned long的别名,
将类型为size_t的length转换为int并不是一个好的做法。可是由于size_t是无符号类型i >= 0的边界条件便会失效……
因此,我们需要用将i >= 0转换为适用于size_t类型的写法:
size_t len = 0, pos = 0, i = str.length();
while (i--) {
// ...
}
return str.substr(pos, len);
最后,我们加上主函数:
int main() {
string str;
getline(cin, str);
cout << find_last_word(str) << endl;
}