> Intza's Blog

怎么找到英文字符串中的最后一个词

2024/12/12

注:本文由题目《输出最后一个词的长度》拓展而来,以下思路可能稍显怪异

设计一个程序,输入英文字符串,输出其中最后一个词。

先理解题目。我们要找到一个东西,需要抓住它的特点,英文中的“词”具有显而易见的下面几个特点:

  1. 仅由英文字母构成
  2. 词与词之间用空格或标点分隔

针对第一个特点,了解 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};

对于一个空的字符串,其对其取下标零显然是不可能的,这种数据结构的局限性在于,其只能表示至少有一位的词,无法表示一个零长词。 因此,我们面临两种选择:

  1. 继续使用该数据结构,对零长词进行特殊处理
  2. 设计另外一种能够自然表示出零长词的数据结构

显然第二种是更加令人舒适的做法,但我们可能一时很难想到。这时我就不得不提醒诸位:

孔子曰:学而不思则罔,思而不学则殆

翻开 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_tlength转换为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;
}
og:image