20200917-2 词频统计

此作业的要求参见作业要求 20200917-2 词频统计

项目代码地址请点击coding https://e.coding.net/bluestudio/wordcount/wordcount.git , git@e.coding.net:bluestudio/wordcount/wordcount.git。

the git url of the project: https://e.coding.net/bluestudio/wordcount/wordcount.git or git@e.coding.net:bluestudio/wordcount/wordcount.git

WHY BAD CODING!!!! F**k Service.

该作业在截至时间后发生了题目描述修改,请自行关注

词频统计 SPEC

老五在寝室吹牛他熟读过《鲁滨逊漂流记》,在女生面前吹牛热爱《呼啸山庄》《简爱》和《飘》,在你面前说通读了《战争与和平》。但是,他的四级至今没过。你们几个私下商量,这几本大作的单词量怎么可能低于四级,大家听说你学习《构建之法》,一致推举你写个程序名字叫wf,统计英文作品的单词量并给出每个单词出现的次数,准备用于打脸老五。

希望实现以下效果。以下效果中数字纯属编造。

功能1

小文件输入。 为表明程序能跑,结果真实而不是迫害老五,请他亲自键盘在控制台下输入命令。

>type test.txt
My English is very very pool.

>wf -s test.txt
total 5

very    2
my      1
english 1
is      1
pool    1

为了评估老五的词汇量而不是阅读量,total一项中相同的单词不重复计数数,出现2次的very计数1次。因为用过控制台和命令行,你早就知道,上面的">"叫做命令提示符,是操作系统的一部分,而不是你的程序的一部分。此功能完成后你的经验值+10.

功能2

支持命令行输入英文作品的文件名,请老五亲自录入。

>wf gone_with_the_wand
total  1234567 words

the    5023
a      4783
love   4572
fire   4322
run    3822
cheat  3023
girls  2783
girl   2572
slave  1322
buy     822

此功能完成后你的经验值+30. 输入文件最大不超过40MB. 如果你的程序中途崩了,会被老五打脸,不增加经验值。

功能3

支持命令行输入存储有英文作品文件的目录名,批量统计。

>dir folder
gone_with_the_wand
runbinson
janelove
>wf folder
gone_with_the_wand
total 1234567 words
the 5023
a 4783
love 4572
fire 4322
run 3822
cheat 3023
girls 2783
girl 2572
slave 1322
buy 822
----
runbinson
total 1234567 words

friday    5023
sea       4783
food      4572
dog       4322
run       3822
hot       3023
cood      2783
cool      2572
bible     1322
eat        822
----
janelove
total  1234567 words

love    5023
chat    4783
lie     4572
run     4322
money   3822
inheritance     3023
class   2783
attribute       2572
data    1322
method  822

因为单词量巨大,只列出出现次数最多的10个单词。此功能完成后你的经验值+8.

功能4

从控制台读入英文单篇作品,这不是为了打脸老五,而是为了向你女朋友炫酷,表明你能提供更适合嵌入脚本中的作品(或者如她所说,不过是更灵活的接口)。如果读不懂需求,请教师兄师姐,或者 bing: linux 重定向,尽管这个功能在windows下也有,搜索关键词中加入linux有利于迅速找到。

>wf -s < the_show_of_the_ring

total 176
the  6
a    3
festival   2
dead 2
for 2
...
或

>wf
A festival for the dead is held once a year in Japan. The festival is
a cheerful occation, for the dead are said to return to their homes
and they are welcomed by the living.

total 176
the  6
a    3
festival   2
dead 2
for 2
...

此功能完成后你的经验值+10.

功能实现

(以上每1经验值对应 1分。)

Q1: 要求在同一个可执行程序中实现全部功能,而不是每个功能单独用一个可执行程序实现。

A:

程序整体代码如下。

// wf.cs
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
using System.IO;

namespace wf
{
    class Program
    {
        /// TextWordsCount
        /// count the words in a string
        /// text:string the string wait to count
        /// counterDic: word counter dictionary, which is the key-value of word count
        static void TextWordsCount(string text, ref Dictionary<string, int> counterDic)
        {
            try
            {
                // prepare text use rules
                // replace ., ", \r, \n as " "(space)
                text = Regex.Replace(text, "\\.|,|\\\"|\\r|\\n", " ");
                // delete continuous " "(space) until last one space
                text = Regex.Replace(text, " +", " ");
                // lower all char in text string
                text = text.ToLower();

                // remove first char if it's space
                if (text.Length > 0 && ' ' == text[0])
                {
                    text = text.Remove(0, 1);
                }

                // remove last char if it's space
                if (text.Length > 0 && ' ' == text[text.Length - 1])
                {
                    text = text.Remove(text.Length - 1, 1);
                }

                // do nothing if this is a empty string
                if (text.Length == 0)
                {
                    return;
                }

                // split text string to words list by space
                foreach (var word in text.Split(" "))
                {

                    try
                    {
                        // try add a new key(word) - value(1) to counterDic
                        // if word not exist in keys, successful
                        // if exist, throw a exception and and that key's value
                        counterDic.Add(word, 1);
                    }
                    catch (ArgumentException)
                    {
                        counterDic[word]++;
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// FileWordsCount
        /// count words in a file, which do that by TextWordsCount line by line
        /// filePath:string file path
        static Dictionary<string, int> FileWordsCount(string filePath)
        {
            string line;
            var counter = new Dictionary<string, int>();
            try
            {
                // file stream
                System.IO.StreamReader file = new System.IO.StreamReader(filePath);
                // read line by line form file stream
                while ((line = file.ReadLine()) != null)
                {
                    try
                    {
                        // count word of the line
                        TextWordsCount(line, ref counter);
                    }
                    catch (Exception)
                    {
                        // get a exception, skip to next line
                        continue;
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return counter;
        }

        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                if (args.Length == 1)
                {
                    if (args[0] == "-s")
                    {
                        Console.WriteLine("Please specise file path.");
                    }
                    else
                    {
                        // for folder
                        if (Directory.Exists(args[0]))
                        {
                            string[] dirs = Directory.GetFiles(args[0]);
                            foreach (var dir in dirs)
                            {
                                if (File.Exists(dir))
                                {
                                    try
                                    {
                                        var counter = FileWordsCount(dir);
                                        var counterList = new List<KeyValuePair<string, int>>(counter);
                                        var l = counterList.OrderByDescending(obj => obj.Value).Take(20);

                                        Console.WriteLine("{0}", dir);
                                        Console.WriteLine("total {0}\n", counterList.Count);
                                        foreach (var item in l)
                                        {
                                            Console.WriteLine("{0} {1}", item.Key, item.Value);
                                        }
                                        Console.WriteLine("--------------------");
                                    }
                                    catch (Exception)
                                    {
                                        Console.WriteLine("{0} faild, skip and continue.", dir);
                                    }
                                }
                                else
                                {
                                    Console.WriteLine("{0} not exists.", dir);
                                }
                            }
                        }
                        else
                        {
                            Console.WriteLine("{0} not exists.", args[0]);
                        }
                    }
                }
                else if (args.Length == 2)
                {
                    // for single file
                    if (args[0] == "-s")
                    {
                        try
                        {
                            var counter = FileWordsCount(args[1]);
                            var counterList = new List<KeyValuePair<string, int>>(counter);
                            var l = counterList.OrderByDescending(obj => obj.Value).Take(20);
                            Console.WriteLine("total {0}\n", counterList.Count);
                            foreach (var item in l)
                            {
                                Console.WriteLine("{0} {1}", item.Key, item.Value);
                            }
                        }
                        catch (Exception)
                        {
                            Console.WriteLine("file {0} faild, check and try again.", args[1]);
                        }
                    }
                }
                else
                {
                    Console.WriteLine("arguments error, check and retry.");
                }
            }
            else
            {
                // for standard input
                var text = Console.ReadLine();
                var counter = new Dictionary<string, int>();
                TextWordsCount(text, ref counter);
                var counterList = new List<KeyValuePair<string, int>>(counter);
                var l = counterList.OrderByDescending(obj => obj.Value).Take(20);
                Console.WriteLine("total {0}\n", counterList.Count);
                foreach (var item in l)
                {
                    Console.WriteLine("{0} {1}", item.Key, item.Value);
                }
            }

        }
    }
}

运行效果如下列图例所示。

fig 1: 功能1运行效果图

功能1运行效果图

fig 2: 功能2运行效果图

功能2运行效果图

fig 3: 功能3运行效果图

功能3运行效果图

fig 4: 功能4运行效果图

功能4运行效果图

博客

(10分)

Q2: 发表博客,介绍上述“项目”中每个功能的重点/难点,展示重要代码片断,给出执行效果截图,展示你感觉得意、突破、困难的地方。

代码片断要求1 凡不缩进的,此题目拒绝接收。不知道什么是“缩进”的同学,请自行补课,不接受以“不知道”作为理由。

代码片断要求2 要求使用cnblogs代码控件,参见往届同学黄兴、宫成荣的作业。凡粘贴IDE中的代码截图,或者贴文字而没有关键字高亮或彩色的,0分。

[http://www.cnblogs.com/huangxman/p/5871201.html]

[http://www.cnblogs.com/gongcr/p/5873493.html]

图表过小、字迹不清、错别字、句子不通顺的,教师会因为读不懂而对此题扣分。

A:

  1. 实现的核心部分为实现文本的分词和统计,本次项目的分词要求以一个脚本为标准,该标准分词脚本代码如下:
cat $1 | tr '\r' '\n' | tr ' ' '\n' |tr A-Z a-z|tr '.' '\n' | tr ',' '\n'|tr '"' '\n'|tr -s '\n'|sort|uniq -c|sort -r |head -n 20

对该脚本进行分析,可知其执行流程为:

1. 获取文件输入,后文称输入的内容为文本;
2. 将文本中的'\r'字符替换为'\n';
3. 将文本中的' '字符替换为'\n';
4. 将文本中的字符全部转换为小写字符;
5. 将文本中的'.'字符替换为'\n';
6. 将文本中的','字符替换为'\n';
7. 将文本中的'"'字符替换为'\n';
8. 将文本中**连续**的'\n'去除到只剩一个;
9. 对文本的每行进行排序;
10. 将文本以行为标准去重,并统计每种行(词)的个数;
11. 倒序排序;
12. 输出词频最高的20行文本

通过查看测试文本,可以发现该标准测试脚本有重大缺陷,其未能有效的处理各种特殊字符,但要求以该脚本为标准,故程序将尽量模拟实现该脚本(有问题的)分词模式。核心代码如下所示。其要点在于通过正则表达,模拟标准分词脚本的替换过程,将文本清洗后进行划分单词,并多每一个词进行统计。统计使用了key-value型数据结构,key即对应的单词,value即词频。该分词逻辑是为了尽可能模拟一个测试脚本实现的,并不能很好的分词,望知悉

        /// TextWordsCount
        /// count the words in a string
        /// text:string the string wait to count
        /// counterDic: word counter dictionary, which is the key-value of word count
        static void TextWordsCount(string text, ref Dictionary<string, int> counterDic)
        {
            try
            {
                // prepare text use rules
                // replace ., ", \r, \n as " "(space)
                text = Regex.Replace(text, "\\.|,|\\\"|\\r|\\n", " ");
                // delete continuous " "(space) until last one space
                text = Regex.Replace(text, " +", " ");
                // lower all char in text string
                text = text.ToLower();

                // remove first char if it's space
                if (text.Length > 0 && ' ' == text[0])
                {
                    text = text.Remove(0, 1);
                }

                // remove last char if it's space
                if (text.Length > 0 && ' ' == text[text.Length - 1])
                {
                    text = text.Remove(text.Length - 1, 1);
                }

                // do nothing if this is a empty string
                if (text.Length == 0)
                {
                    return;
                }

                // split text string to words list by space
                foreach (var word in text.Split(" "))
                {

                    try
                    {
                        // try add a new key(word) - value(1) to counterDic
                        // if word not exist in keys, successful
                        // if exist, throw a exception and and that key's value
                        counterDic.Add(word, 1);
                    }
                    catch (ArgumentException)
                    {
                        counterDic[word]++;
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
  1. 功能点1/功能点2实现。由于功能点1/功能点2要求和功能是相同的,故合并实现。该功能的要点是读取文本,并调用TextWordsCount()对文本进行词频统计。题目要求可以进行大文件读取,为了减低对计算机资源的压力,应当使用文件流实现功能,使得可以边读取边处理。代码如下所示。本文的实现中,是用文件流实现对文件的读取,并且读取一行,处理一行,并不缓存文本数据。
        /// FileWordsCount
        /// count words in a file, which do that by TextWordsCount line by line
        /// filePath:string file path
        static Dictionary<string, int> FileWordsCount(string filePath)
        {
            string line;
            var counter = new Dictionary<string, int>();
            try
            {
                // file stream
                System.IO.StreamReader file = new System.IO.StreamReader(filePath);
                // read line by line form file stream
                while ((line = file.ReadLine()) != null)
                {
                    try
                    {
                        // count word of the line
                        TextWordsCount(line, ref counter);
                    }
                    catch (Exception)
                    {
                        // get a exception, skip to next line
                        continue;
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return counter;
        }
  1. 功能点3的实现,通过调用功能点1/2实现的单个文件读取函数,对文件夹内的全部文件进行处理即可。

  2. 功能点4的实现,通过System.Console.ReadLine()获取到函数输入,之后调用函数TextWordsCount()处理即可。

PSP

(8分)

Q3: 在同一篇博客中,参照教材第35页表2-2和表2-3,为上述“项目”制作PSP阶段表格。PSP阶段表格第1列分类,如功能1、功能2、测试功能1等。

要求1 估算你对每个功能 (或/和子功能)的预计花费时间,填入PSP阶段表格,时间颗粒度为分钟。

要求2 记录词频统计项目实际花费时间,填入PSP阶段表格,时间颗粒度要求分钟。

要求3 对比要求1和要求2中每项时间花费的差距,分析原因。

A:

模块 预计用时 (min) 实际用时 (min) 差距 (min) 原因
功能点1 30 20 -10 需求较为简单明确,很好实现
功能点2 30 80 +50 花费较长时间分析标准脚本的分词规则,并寻找简便方法实现
功能点3 20 10 -10 该功能是有功能点2发展而来,功能点正确实现后,功能点3需求明确简单
功能点4 20 6 -14 复用功能点1/2的核心代码,需求明确简单

代码及版本控制

(5分。虽然只有5分,但此题如果做错,因为教师得不到你的代码,所以会导致“功能实现”为负分。)

Q4: 代码要求在 coding.net 做版本控制。要求push&pull时使用git客户端,不允许使用web页面。

要求频繁checkin。要求在PSP中记录的每次离开键盘5分钟以上,需要checkin。如果你持续长达4小时不离开键盘,教师要求展示此项能力。

推荐git客户端tortoisegit。推荐先pull [https://github.com/weijunying2019102969/novelsfortest.git],里面有测试用例。

要求项目设置为“公开源代码(公开后,任何人都可以访问代码仓库,请慎重考虑!)”,保证教师、助教、其他同学、兄弟院校的师生、教师邀请的专家和老师可以在不需要你授权的情况下访问到源代码。在项目创建时,请参考下图中红框位置。

代码要求1 在同一篇博客中,发布代码的地址,形如 [https://github.com/weijunying2019102969/novelsfortest.git]。

代码要求2 代码规范要求,凡不缩进的,此题目拒绝接收。不知道什么是“缩进”的同学,请自行补课,不接受以此作为理由。

本版本对于使用语言没有要求。不过考虑到下周作业将有效能分析,所以建议谨慎选择语言。推荐VS环境。不反对本周使用你熟悉的语言,下周再迁移到适合的语言。建议不要通过本作业学习不熟悉的语言,完成任务优先。

教师会使用脚本pull所有项目,如果你的提交不符合规范,非人力所能关怀。

A:

项目代码地址请点击coding https://e.coding.net/bluestudio/wordcount/wordcount.git , git@e.coding.net:bluestudio/wordcount/wordcount.git。

the git url of the project: https://e.coding.net/bluestudio/wordcount/wordcount.git or git@e.coding.net:bluestudio/wordcount/wordcount.git

WHY BAD CODING!!!! F**k Service.

测试

“从控制台读入英文单篇作品”中的英文作品,包括不限于[https://github.com/weijunying2019102969/novelsfortest.git]中的测试用例。大量英文原版(无版权,不是盗版)的测试用例在[http://www.gutenberg.org/]可以找到。

有同学问过“什么是单词”,教师也不确认。

我们在此约定,以下面的数据作为对单词定义(及分界)的猜测。以[https://github.com/weijunying2019102969/novelsfortest.git]中的wordfreq.sh的运行结果为标准,如果你的程序得到的单词总数与这一脚本统计结果相同,那么对单词的定义符合要求。WORD统计以上测试用例中《战争与和平》568,286单词,统计 The Dead Return共189词。即,如果你的程序得到的单词总数与WORD相同,那么对单词的定义符合要求。这一要求作为用户需求,值得质疑和改进。比如,cat和cats算不算同一个单词,do和did呢?估且如此,以wordfreq.sh脚本的运行结果作为需求范围的边界。

教师不仅会使用[https://github.com/weijunying2019102969/novelsfortest.git]中公开的测试用例,也会使用未在此通知你的测试用例。

命令行参数、重定向、标准输入、控制台,是重要概念。如果你此前并未掌握握,一定要先行学习再提交作业。

凡不符合要求的,虽然你可能花费很多辛苦,教师在分数上爱莫能助。

posted @ 2020-09-22 23:55  WenqiangXie  阅读(225)  评论(0编辑  收藏  举报