代码改变世界

统计一篇英文文章中的单词数

2013-10-31 18:15  kingshow  阅读(6575)  评论(0编辑  收藏  举报

写英语作文的时候,常常要求满足一定的字数,在以往,要么,我们一个一个地数,要么,我们估算一行的单词数,然后用行数进行估算。第一种方法太费时,如果要是写个长篇大论,那几乎是mission imposible,而第二种方法有不太准确。这就给我们留下了一个问题:如何又快又准确地统计一篇英文文章中的单词数?

 

程序,就是用来帮助人们完成这些看起来枯燥繁琐但是带有一定规律性的事情的。

 

要解决这个问题,最自然的想法是,读取文章的所有内容(用fopen()和fgets()),然后一个单词一个单词地统计,然而,我们在这里遇到了一个难题:程序看不懂英文,他如何知道什么是一个单词,什么不是一个单词呢?我们似乎在这里遇到了障碍,可是,如果我们换个角度思考问题,也许会柳暗花明又一村:我们注意到,文章中的单词都是用空格间隔开的,那么换句话说,也就是

单词数=空格数+1

程序不认识单词,但是程序认识空格啊。这样,整个问题实际上转换为了统计文章中的空格数。

有了这样的问题转换,整个问题就简单多了。你可以先按照这个思路自己实现,先来看看这个小例子代码实现:

/*
 * wordcounts1.c
 *
 *  Created on: 2013年10月31日17:15:00
 *  Author: Bruce
 */
#include <stdio.h>
#include <string.h>
int main()
{
    //假设需要统计的字符串都保存在str字符数组中
    char str[128] = "This is a C program!";
    //用于保存空格字符数的变量
    int numWhiteSpace = 0;
    //循环遍历整个字符数组,统计空格字符数
    int i = 0;
    while('\0' != str[i])
    {
        //判断当前字符是否是空格
        if(' ' == str[i]){
            ++numWhiteSpace;   //空格数加1
        }
        ++i; //检查下一个字符
    }
    //输出字符串中的单词数,也就是空格数加1.
    printf("There are %d words in \"%s\"",numWhiteSpace+1,str);
    return 0;
}

 

     简单的29行代码,就可以帮我们数出一个字符串中的单词数, 学了C语言,解决问题就是这么简单啦。 这里,我们只是简单地统计了一个简单字符串中的单词数,那么,如何对一篇英文文章中的单词数进行统计呢? 如何对这个程序进一步优化使其更加合理呢?欲知后事如何,且看下面。 学了C语言,如何写一个程序统计一篇英文文章中的单词数?(续)

     要在原程序的基础上进行扩展,实现对文章中单词数的统计,我们有两个工作要做:

  1. 将原来全部放在main()函数中进行的单词统计功能抽取出来,单独成一个独立的函数countword(),它可以接受一个char*类型的参数,返回的是这个指针所指向的字符串中的单词数。这里需要注意的是,因为在文章中,换行也会分割单词,所以我们也应将换行字符’\n’统计在内。
  2. 单独地利用fopen()以及fgets()等操作文件的函数,将文件中的字符串读取到一个字符数组中,供上一步完成的countword()函数进行统计。这些工作都提取到read()函数中完成。

经过这样的分析我们程序在main()函数中要完成的工作也就清晰了,首先用read()函数读取文件到一个字符数组,然后用countword()函数对读取得到的字符串中的单词数进行统计。

经过这样的分析,再结合我们在《C程序设计伴侣》中学到的文件读写,字符串处理,main()函数的参数等知识,整个程序的实现就很简单了。

/*
 * wordcounts2.c
 *
 *  Created on: 2013年10月31日17:15:00
 *  Author: Bruce
 */
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
//读取文件到字符数组中
bool read(const char * file,char * str)
{
    if(NULL == file || NULL == str)
    {
        return false;
    }
    //以只读方式打开文件
    FILE * fp = fopen(file,"r");
    if(NULL != fp)
    {
        //用fgets()函数读取文件
        char line[128] = "";
        while(fgets(line,128,fp) != NULL)
        {
            //将读取得到的字符串保存到目标数组中
            strcat(str,line);
        }
        //关闭文件
        fclose(fp);
        fp = NULL;
        return true;
    }
    else
    {
        printf("Cannot open %s.",file);
        return false;
    }
}
int countWord(char * text)
{
    if(NULL != text)
    {
        //循环遍历整个字符数组,统计空格字符以及换行字符数
        int i = 0;
        int num = 0; //空格或换行字符数
        while('\0' != text[i])
        {
            //判断当前字符是否是空格或者换行字符,如果是,则统计
            if(' ' == text[i] || '\n' == text[i])
            {
                ++num;
            }
            ++i; //检查下一个字符
        }
        return num+1; // 返回单词数目
    }
    else
    {
        return 0;
    }
}
int main( int argc, char * argv[])
{
    if(argc != 2)
    {
        puts("ARGUMENS ERROR. eg. count demo.txt");
        return 1;
    }
    char text[1028*10] = "";
    if(read(argv[1],text))
    {
        int n = countWord(text);
        // 输出字符串中的单词数,也就是空格数加1
        printf("there is(are) %d word(s) in the \"%s\"",n,argv[1]);
    }
    return 0;
}

然后,用

gcc countword.c –o countword.exe

将其编译成countword.exe应用程序,并用下面的命令统计一篇文章的单词数:

countword.exe demo.txt

这样,我们就可以用这个程序统计一篇英文文章中的单数了,再也不用一个个数的头晕眼花了。这正是体现了程序的作用,帮助人类又快又好地完成一些繁琐而有规律的工作。

可是,当用过一段时间后,我们却发现程序统计的结果并不是那么准确,有时会多统计几个单词,这又是为什么呢?这个新问题又该如何解决呢?欲知后事如何,在往下看。

      上面说到我们对字符数实现简单扩展,使其可以统计一整片文章中的单词数。然而在使用的时候,我们却发现其统计的单词数并不是很准确,有时候多统计了,有时候又少统计了。当程序的执行结果与我们的预期相左时,这就意味着我们的程序中出现了臭虫,也就是传说中的Bug。

      那么这个臭虫在哪儿呢?如何找到具体的位置并把他消灭呢?这时就需要用到我们的调试技术了。所谓调试,也就是在程序中设置断点(程序执行过程中暂停的位置,在Visual Studio中,可以使用F9在光标当前行设置断点,当在调试模式下,即用F5调试运行程序时,程序会在此暂停,这样我们就可以观察程序的中间运行情况。),然后用F10单步运行程序,观察程序的执行情况同算法的预想情况有什么差别,当出现差别的地方,就是Bug所在的地方。通过调试,我们发现在原来的程序中有这样两个问题:

  1. 错误地对连续的多个空格字符进行了统计。连续的多个空格字符,也只是起一次分割作用,但是程序却不管这些,只要遇到空格就统计在内,将多个空格分隔的两个单词统计成了多个单词,所以产生了多统计的情况。
  2. 没有统计标点符号。除了空格之外,在文章中起分割作用的还有标点符号,比如逗号,句号等等,这些分割符号都没有统计在内,所以用逗号分割的两个单词被统计成了一个单词,因而出现了少统计的情况。

我们找到了产生Bug的原因,那么如何解决呢?

     很自然的想法就是见招拆招。对第一个bug,一种方式是对文章进行预处理,删除其中的冗余的空格,另一种方法是将冗余的连续多个空格字符替换为有效字符,这两种方法都是为了达到只保留一个空格字符的目的。

面对第二个bug,解决办法就是将其他分割字符也统计在内,包括逗号,句号,问号,感叹号,冒号,省略号,破折号,前引号,后引号,斜杠,反斜杠,等等等等。

这样的解决方法虽然简单直观,可是却比较难以实现:第一个,需要对文章进行预处理,找到并删除其中的冗余空格不是一件容易的事情,需要遍历整个字符串。第二个,文章中的分割符号种类举不胜举,如何保证所有分割符号都统计在内了?看来,上面的方法行不太通,我们只好另辟蹊径了。

回到问题的原点,我们要统计的是一篇文章中的单词数,我们注意到单词有这样一个简单的特征:总是以非字母字符(比如,空格,逗号)后的字母字符开始,并用字母字符一直延续,直到再次遇到非字母字符作为结束,利用这个特征,我们就可以定义确定一个单词了。

     另外,我们还注意到这样一个规律:整篇文章的单词数等于已经找到的单词数加上剩余部分的单词数,像这种可以把一个大问题拆分成多个相似的小问题来解决的情况,最自然的解决方法就是递归(参考《C程序设计伴侣》的7.6 函数的递归调用——统计字符串出现的次数 函数的递归调用)这种思路来解决。

按照上面的简单分析,我们可以将整个程序改写如下:

/*
 * wordcounts3.c
 *
 *  Created on: 2013年10月31日17:15:00
 *  Author: Bruce
 */
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
//读取文件到字符数组中
bool read(const char * file,char * str)
{
    if(NULL == file || NULL == str)
    {
        return false;
    }
    //以只读方式打开文件
    FILE * fp = fopen(file,"r");
    if(NULL != fp)
    {
        //用fgets()函数读取文件
        char line[128] = "";
        while(fgets(line,128,fp) != NULL)
        {
            //将读取得到的字符串保存到目标数组中
            strcat(str,line);
        }
        //关闭文件
        fclose(fp);
        fp = NULL;
        return true;
    }
    else
    {
        printf("Cannot open %s.",file);
        return false;
    }
}
//统计字符串中单词数
int countWord(char * text)
{
    //开始寻找开始位置
    //一直从字符串开始位置向后寻找
    //直到遇到一个字母字符
    while(!isalpha(*text))
    {
        // 如果找到了字符串结束位置,
        // 意味着整个字符串寻找完毕,
        // 结束整个递归
        if('\0' == *text)
        {
            return 0;
        }
        ++text; // 查找下一个字符
    }
     while(isalpha(*text))
     {
         // 如果当前字符是字母字符
         // 则继续查找下一个字符
         ++text;
     }
     return 1 + countWord(text);
}
int main( int argc, char * argv[])
{
    if(argc != 2)
    {
        puts("ARGUMENS ERROR. eg. count demo.txt");
        return 1;
    }
    char text[1028*10] = "";
    if(read(argv[1],text))
    {
        int n = countWord(text);
        // 输出字符串中的单词数,也就是空格数加1
        printf("there is(are) %d word(s) in the \"%s\"",n,argv[1]);
    }
    return 0;
}

    经过这样的改进,这个程序就可以处理比较复杂混乱的文章了,例如,下面这么神经分裂的文章,他都能够正确处理:

tought is a seed,d ? and action i,… s a tree .

     到这里,整个统计文章中单词数的问题算是比较圆满地解决了。这里只是简单地分析了一下解决问题的思路,要想完全理解并掌握,还需要进一步深入的学习C语言的知识并熟练运用。这是《C程序设计伴侣》中的一个实际例子的改变,书中还有很多其他类似的有趣的例子,有兴趣的同学可以参考。

最后发现,学了C语言,还是可以做很多事情的,农民用镰刀改变世界,工人用斧头改变世界,我们用代码改变世界。自豪吧,程序员!

转自:好知网