字符串处理

经过前面的学习,我们已经掌握了很多编程的知识:变量、数据类型、列表、迭代以及函数!接下来我们把这些知识综合在一起构建一个复杂一些的作品。当然我们还会继续学习新的知识,本章我们的主题是文本和字符串,我们会通过学习如何处理文本以及字符串来实现一个小小的数据分析的作品,作品的名字叫做阅读难度计算器。

计算可读性

为什么要做这样一个作品呢?或者说这个作品有什么用呢?因为我们现在面临的文章或者说文本很多,不同的文章的难度是不同的,通过我们的程序就可以告诉大家一篇文章的难度到底如何,是适合小学生阅读还是适合博士生阅读,相当于我们给一篇文章做了一个等级测评。那如何计算这个阅读难度呢?对于英文文章的难度计算其实是有个依据的,1948年Flesch博士提出了这样一个公式: 阅读难度 = 206.835 - 1.015(单词总数/句子总数)- 84.6(音节总数/单词总数) 也就是说我们需要计算一下一篇文章中的单词的数量、句子的数量以及音节的数量,然后通过这个公式就可以计算出来一个难度的数值了。这个数值我们又可以通过这样一个映射关系得到这篇文章具体的难易程度。

分数年级说明
100.00~90.005年级非常容易阅读。对于平均年龄11岁的学生来说,很容易理解
90.0~80.06年级易于阅读。相当于会话英语水平
80.0~70.07年级比较容易阅读
70.0~60.08/9年级浅显的英语。13到15岁的学生很容易理解
60.0~50.010~12年级比较难阅读
50.0~30.0大学生很难阅读
30.0~0.0大学毕业非常难阅读。大学毕业生才能很好地理解

计划

通过这个计算方法的学习,我们能看到其实核心在于如何得到单词、句子以及音节的数量,公式本身是比较简单的。所以我们的计划就是:

  1. 计算单词的个数:我们需要将一段文本中的单词分解出来然后统计单词的个数;
  2. 计算句子的个数:我们需要将一段文本中的句子分解出来然后统计句子的个数;
  3. 计算音节的个数:我们需要将每个单词中的音节数量计算出来,然后汇总到一起得到最终的音节的个数;

伪代码

接下来我们根据刚才的计划来写一些伪代码,以便进一步整理我们的思路。在后面的课程中我们逐步将伪代码替换为真正的代码。

'''
定义一个函数computer_readability(text):
    声明一个变量total_words用于记录单词个数并初始化为0
    声明一个变量total_sentences用于记录句子个数并初始化为0
    声明一个变量total_syllables用于记录音节个数并初始化为0
    声明一个变量score用于记录最终得分并初始化为0

    调用count_words函数计算单词数量并将结果存到total_words变量中
    调用count_sentences函数计算单词数量并将结果存到total_sentences变量中
    调用count_syllables函数计算单词数量并将结果存到total_syllables变量中

    根据公式计算score
    socre = 206.835 - 1.015*(total_words / total_sentences) - 84.6 * (total_syllables / total_words)
    
    if score >= 90:
        print("5年级阅读难度")
    elif score >= 80:
        print("6年级阅读难度")
    elif score >= 70:
        print("7年级阅读难度")
    elif score >= 60:
        print("8/9年级阅读难度")
    elif score >= 50:
        print("10~12年级阅读难度")
    elif score >= 30:
        print("大学生阅读难度")
    else:
        print("大学毕业生阅读难度")

'''

多行文本

接下来我们开始准备一段真正的文章用于我们后续的分析,之前我们学习过用变量存储字符串的方法:

text = 'The first thing that stands between you'

这种方法适合短一些的字符串,如果字符串很长,这种方法就不太合适了,因为如果我们在编辑器中换行的话会提示错误,如果不换行的话导致阅读起来很麻烦。所以我们用新的方法来存储这种很长的字符串:

text = """The first thing that stands between you and writing your first, real,
piece of code, is learning the skill of breaking problems down into
acheivable little actions that a computer can do for you."""

当然,这里用3个单引号或者双引号都是可以的,但是要保持前后统一。 我们往变量中存一个很长文本的变量放到一个单独的文件中,起名叫text.py,里面加一个print函数测试一下能否正常输出。

text = """The first thing that stands between you and writing your first, real,
piece of code, is learning the skill of breaking problems down into
acheivable little actions that a computer can do for you. Of course,
you and the computer will also need to be speaking a common language,
but we'll get to that topic in just a bit.
Now breaking problems down into a number of steps may sound a new
skill, but its actually something you do every day. Let’s look at an
example, a simple one: say you wanted to break the activity of fishing
down into a simple set of instructions that you could hand to a robot,
who would do your fishing for you. Here’s our first attempt to do that,
check it out:
You can think of these statements as a nice recipe for fishing. Like any
recipe, this one provides a set of steps, that when followed in order,
will produce some result or outcome in our case, hopefully, catching
some fish.
Notice that most steps consists of simple instruction, like "cast line
into pond", or "pull in the fish." But also notice that other
instructions are a bit different because they depend on a condition,
like “is the bobber above or below water?". Instructions might also
direct the flow of the recipe, like "if you haven’t finished fishing,
then cycle back to the beginning and put another worm on the hook."  
Or, how about a condition for stopping, as in “if you’re done” then go
home.
You’re going to find these simple statements or instructions are the
first key to coding, in fact every App or software program you’ve ever
used has been nothing more than a (sometimes large) set of simple
instructions to the computer that tell it what to do."""

print(text)

建立函数

接下来我们开始把伪代码逐步变成Python代码。首先定义函数:

# compute:计算
# readability:阅读难度
# total:总共的
def compute_readability(text):
    total_words = 0  # 单词总数
    total_sentences = 0  # 句子总数
    total_syllables = 0  # 音节总数
    score = 0  # 得分

计算的函数有了,是不是还得提供具体计算的文本以及实际调用函数呐,我们来引入刚才创建的text.py文件,并且把其中的变量作为参数传到函数中进行调用。这里做一个小小的修改,将print函数从text.py中移到6-1.py文件中。

import ch1text
# compute:计算
# readability:阅读难度
# total:总共的
def compute_readability(text):
    total_words = 0  # 单词总数
    total_sentences = 0  # 句子总数
    total_syllables = 0  # 音节总数
    score = 0  # 得分
    print(text)
compute_readability(ch1text.text)

计算单词总数

接下来我们开始计算单词的个数,要实现这个功能我们要学习一个新的函数split,这个函数可以把一个字符串分解开来,举个例子:

text = "good morning, li lei."
words = text.split()
print(words)

split函数默认以空格作为分割符来讲字符串分割开来形成列表,也支持传入其他的分隔符,比如逗号、句号,我们来测试一下。

text = "good morning, li lei."
words = text.split(',')
print(words)

好的,接下来我们把这个函数融入到我们计算阅读难度的函数中:

import text
# compute:计算
# readability:阅读难度
def compute_readability(text):
    total_words = 0  # 单词数量
    total_sentences = 0  # 句子数量
    total_syllables = 0  # 音阶数量
    score = 0  # 得分
    words = text.split()
    total_words = len(words)
    print(words)
    print(total_words,'个单词')
# compute:计算
# readability:阅读难度
compute_readability(text.text)

到这里我们就完成了单词个数的计算,你能想办法完成句子个数的计算吗?

计算句子总数

伪代码中下一步是计算文本中的句子个数,这个怎么计算呢?首先我们得明确句子的定义:以句号、分号、问号、感叹号这些终止符号为结尾的字符串。那我们是不是把字符串中所有的字符都迭代处理一下,看一看有多少个终止符号就可以了?确实是这样。那我们怎么来迭代处理字符串呢?先来看一个例子:

text = "good morning, li lei."
for char in text:
    print(char)

这三行代码就可以轻松实现把字符串中每一个字符迭代打印出来,代码是不是很熟悉,是的,我们之前在迭代列表的时候也是这样用的,其实在这里字符串可以认为是一个字符列表,所以用for循环迭代的方法是一样的。 回到我们的代码,我们可以这样来实现这个功能:

import text
# count:数数,数量
# sentence:句子
def count_sentences(text):
    count = 0
    for char in text:
        if char == '.' or char == ';' or char == '?' or char == '!':
            count = count + 1
    return count
# compute:计算
# readability:阅读难度
def compute_readability(text):
    total_words = 0  # 单词数量
    total_sentences = 0  # 句子数量
    total_syllables = 0  # 音阶数量
    score = 0  # 得分
    words = text.split()
    total_words = len(words)
    total_sentences = count_sentences(text)
    print(total_words, '个单词')
    print(total_sentences, '个句子')
# compute:计算
# readability:阅读难度
compute_readability(text.text)

这样的话我们就实现了计算句子个数的功能,接下来我们讲一个可以简化这段代码的方法:in操作符。举个例子:

names = ['李雷','小明','小花','王朋']
if '李雷' in names:
    print("是的,他在列表里")
else:
    print("不,他不在列表里")

那我们怎么利用in操作符来简化我们计算句子个数的代码呢?

def count_sentences(text):
    count = 0
    # terminal:结尾
    terminals = '.;?!'
    for char in text:
        if char in terminals:
            count = count + 1
    return count

当然,terminals这个变量用列表形式来存储句号等终止符号也是可以的。对于for循环迭代处理来说,字符串跟列表是一样的操作,所以我们说字符串是特殊的字符列表,那它特殊在哪里呢?来看下这个例子:

terminals1 = ['.',';','?','!']
terminals2 = '.;?!'
print(terminals1[1])
print(terminals2[1])
terminals1[1] = 'a'
terminals1[2] = 'a'

执行下这个程序会发现最后一行代码报错了,为什么呢?是因为字符串内的字符是不能被改变的,所以这里的代码尝试去修改字符串的时候就报错了!那如果我们想要修改一个字符串怎么办呢,比如我想把hello中的l都换成数字1,你可以思考一下有没有什么方法可以做到~

计算音节数

本章节我们来实现计算音节的数量,在此之前我们先把上次遗留的思考题来解答一下:如果我们想要修改一个字符串怎么办呢?

text = 'hello'  # he11o
text1 = ''
for char in text:
    if char == 'l':
        text1 = text1 + '1'
    else:
        text1 = text1 + char
print(text1)

因为英语中对于音节没有完美的定义,所以要计算一段文本中有多少个音节并没有一个统一的算法,在这里我们用如下的算法来计算音节数量:

  1. 如果一个单词少于3个字符,就记为一个音节;
  2. 否则,统计元音的个数,并以此表示音节个数;
  3. 为了使上一步更准确,删除单词中所有连续的元音;
  4. 考虑到不发音的e,删除单词末尾的e;
  5. 如果y是最后一个字符,就把它当做一个元音; 从这个规则中能看出,我们需要对每一个单词来计算其中的音节个数,最后再汇总到一起,所以我们先来一下代码框架:
# count:数数,数量
# syllable:音节
# word:单词
def count_syllables(words):
    count = 0
    for word in words: #遍历
        word_count = count_syllables_in_word(word)
        count = count + word_count
    return count
def count_syllables_in_word(word):
    count = 0
    return count

那具体怎么计算一个单词中的音节数量呢?我们按照上面的规则逐步进行。

统计元音

第1个规则比较简单,直接用len函数就可以了,统计元音则可以用迭代的方法来实现:

def count_syllables_in_word(word):
    count = 0

    if len(word) <= 3:
        return 1

    vowels = "aeiouAEIOU"
    for char in word:
        if char in vowels:
            count = count + 1
    return count

不过我们还得进一步考虑两个或者多个连续的元音,比如单词book,我们希望只统计第1个o而忽略第2个o,这样就总共有1个音节而不是两个了。所以总结一下我们的目标就是:扫描各个单词中的每个音符,看它是不是元音,不过,遇到一个元音后我们要确保忽略它之后的所有元音,直到看到另一个辅音。在此之后,重复这个过程直到到达单词末尾。 我们通过引入一个布尔值的变量来实现这个功能:

def count_syllables_in_word(word):
    count = 0
    if len(word) <= 3:
        return 1
    vowels = "aeiouAEIOU"
    prev_char_was_vowel = False
    # prev:前一个,previous的缩写
    # char:字符
    # was:是,is的过去式
    # vowel:元音
    for char in word:
        if char in vowels:
            if not prev_char_was_vowel:
                count = count + 1
            prev_char_was_vowel = True
        else:
            prev_char_was_vowel = False
    return count

以“roomful”单词为例,单步调试一下这个程序,观察char,prev_char_was_vowel以及count变量的变化。 好的,到这里我们就实现了规则中的前3步,后两步是关于末尾字符的处理,你可以先思考下有没有什么好的方法。

切片

现在要计算音节的个数我们还剩下两个任务:删除最后的e和统计最后的y。实际上,我们还忘了一件事,有些单词末尾还有标点符号,这可能会让检查最后的e和y变得复杂。比如单词分割后我们能看到"first,","you."这样的单词,所以我们先删除这些标点符号。为了实现这个功能,我们来学习一个新的知识:切片。举个例子:

text = "good morning, li lei."
sub_string = text[2:7]
print(sub_string)

sub_string = text[:7]
print(sub_string)

sub_string = text[8:]
print(sub_string)

sub_string = text[10:-1]
print(sub_string)

sub_string = text[-10:]
print(sub_string)

切片的操作确实让字符串的处理变得简单了一些,而且!切片不只是用于字符串,还可以用于列表!

names = ['李雷','小明','小花','王朋']
print(names[1:3]
print(names[1:-1]

回到我们的音节计算函数,我们可以使用切换很容易的将末尾的字符删除或者进行判断:

def count_syllables_in_word(word):
    count = 0
    # ending:结尾
    # last:最后一个
    # char:字符
    endings = '.,;!?:'
    last_char = word[-1]
    # process:操作过程
    if last_char in endings:
        processed_word = word[0:-1]
    else:
        processed_word = word
    if len(processed_word) <= 3:
        return 1  
    if processed_word[-1] in 'eE':
        processed_word = processed_word[0:-1]
 
    # prev:前一个,previous的缩写
    # char:字符
    # was:是,is的过去式
    # vowel:元音
    vowels = "aeiouAEIOU"
    prev_char_was_vowel = False
    for char in processed_word:
        if char in vowels:
            if not prev_char_was_vowel:
                count = count + 1
            prev_char_was_vowel = True
        else:
            prev_char_was_vowel = False
                
    if processed_word[-1] in 'yY':
        count = count + 1

    return count

好的,到这里我们就完成了音节个数的计算,在加上之前我们已经完成的单词个数、句子个数的计算,现在我们已经完成了计算阅读难度中所需要的所有数据的计算,那后续怎么得出具体难度呢?你可以先思考并大胆尝试一下~

实现阅读难易度公式

本节课我们来收个尾,利用之前计算好的单词个数、句子个数以及音节个数来计算最终的阅读难度,首先我们来根据这个公式计算一个难易度分数:

score = (206.835 - 1.015 * (total_words / total_sentences)
                       - 84.6 * (total_syllables / total_words))
    print('得分:',score)

虽然公式很复杂但是在Python的帮助下还是比较容易计算的,但其实我们最终需要的并不是这个得分,而是一个让用我们程序的人更容易理解的一个阅读难度说明,所以我们接下来把得分做一个转换,依据就是之前的这个划分表了:

def output_results(score):
    if score >= 90:
        print("5年级阅读难度")
    elif score >= 80:
        print("6年级阅读难度")
    elif score >= 70:
        print("7年级阅读难度")
    elif score >= 60:
        print("8/9年级阅读难度")
    elif score >= 50:
        print("10~12年级阅读难度")
    elif score >= 30:
        print("大学生阅读难度")
    else:
        print("大学毕业生阅读难度")

有了这个函数我们把它加到主程序里面就可以顺利完成我们的阅读难度计算器啦!以下是我们本章节完整的代码:

import text
def count_syllables(words):
    count = 0
    for word in words:
        word_count = count_syllables_in_word(word)
        count = count + word_count
    return count
def count_syllables_in_word(word):
    count = 0
    endings = '.,;!?:'
    last_char = word[-1]
    if last_char in endings:
        processed_word = word[0:-1]
    else:
        processed_word = word
    if len(processed_word) <= 3:
        return 1  
    if processed_word[-1] in 'eE':
        processed_word = processed_word[0:-1]
 
    vowels = "aeiouAEIOU"
    prev_char_was_vowel = False
    for char in processed_word:
        if char in vowels:
            if not prev_char_was_vowel:
                count = count + 1
            prev_char_was_vowel = True
        else:
            prev_char_was_vowel = False
    if processed_word[-1] in 'yY':
        count = count + 1
    return count
def count_sentences(text):
    count = 0
    terminals = '.;?!'
    for char in text:
        if char in terminals:
            count = count + 1
    return count
# output:输出
# result: 结果
def output_results(score):
    if score >= 90:
        print("5年级阅读难度")
    elif score >= 80:
        print("6年级阅读难度")
    elif score >= 70:
        print("7年级阅读难度")
    elif score >= 60:
        print("8/9年级阅读难度")
    elif score >= 50:
        print("10~12年级阅读难度")
    elif score >= 30:
        print("大学生阅读难度")
    else:
        print("大学毕业生阅读难度")
def compute_readability(text):
    total_words = 0
    total_sentences = 0
    total_syllables = 0
    score = 0
    words = text.split()
    total_words = len(words) #word:单词
    total_sentences = count_sentences(text) #sentence:句子
    total_syllables = count_syllables(words) #syllable:音节
    score = (206.835 - 1.015 * (total_words / total_sentences)
                       - 84.6 * (total_syllables / total_words))
    print('得分:',score)
    # 根据阅读系数输出结果
    output_results(score)
compute_readability(text.text)
Last Updated:
Contributors: houlinsen