热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

软工之词频统计器及基于sketch在大数据下的词频统计设计

目录摘要算法关键红黑树稳定排序代码框架.h文件:.cpp文件频率统计器的实现接口设计与实现

目录

  • 摘要
  • 算法关键
    • 红黑树
    • 稳定排序
  • 代码框架
    • .h文件:
    • .cpp文件
    • 频率统计器的实现
    • 接口设计与实现
      • 接口设计
      • 核心功能词频统计器流程
  • 效果
  • 单元测试
  • 性能分析
    • 性能分析图
    • 问题发现
    • 解决方案
  • 异常处理
  • PSP表格记录
  • 感想
  • 基于sketch在大数据下的词频统计设计
    • 引言
    • 背景
    • 解决方案
  • 总结
  • 参考文献:

Github项目地址

摘要
  • 本词频统计器包括行数统计、字符数统计、单词数统计、词频统计功能。基于红8黑树算法稳定排序实现,其中红黑树算法为本词频统计器提供良好的效率提供性能下限保证提供词频统计的高性能提供较小的资源开销,而稳定排序算法提供了排序的稳定性,保证了词频统计结果按照字典序生成。本词频统计器基于C++实现,如下图所示,统计器作为对象,具有五个成员函数,分别实现统计器的五个功能,而功能函数提供了稳定排序等功能。并设计了异常处理函数,解决一定场景下的异常问题。

算法关键

红黑树

红黑树是一种自平衡的二叉查找树,是一种高效的查找树。
本词频统计器基于红黑树算法,具有以下优点:

  • 提供良好的效率:可在O(logN)时间内完成查找、增加、删除等操作,能保证在最坏情况下,基本的动态几何操作的时间均为O(lgn)。只要求部分地达到平衡要求,降低了对旋转的要求,任何不平衡都会在三次旋转之内解决,从而提高了效率。本词频统计器设计大量增删改查操作,红黑树算法提供了良好的效率支撑。

  • 提供性能下限保证:相比于BST,红黑树可以能确保树的最长路径不大于两倍的最短路径的长度,可见其查找效果的最低保证。最坏的情况下也可以保证O(logN)的复杂度,好于二叉查找树O(N)复杂度。在大数据量情况下,红黑树算法为词频统计器提供良好的性能保证。

  • 提供词频统计的高性能:红黑树的算法时间复杂度和AVL树相同,但统计性能更高。插入 AVL树和红黑树的速度取决于所插入的数据。在数据比较杂乱的情况,则红黑树的统计性能优于AVL树。在词频统计时,数据分布较为杂乱,在此应用场景下,红黑树算法与词频统计器契合。

  • 提供较小的资源开销:与基于哈希的算法相比,基于红黑树方法带来更小的资源开销,程序消耗内存较少。哈希的算法占用大量资源,需要维护大量的计数器,并且在哈希过程中消耗了大量的计算资源。本词频统计器消耗的资源较少

稳定排序

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的。本词频统计器利用了稳定排序。

  • 稳定性;词频统计后,要求按字典序输出,而经过红黑树处理后,以达到字典序的要求,若使用非稳定排序,虽性能较高,但打乱了原先的字典序。经过稳定排序后,相对次序保持不变,仍为字典序,满足要求
  • 达到一定性能:本词频统计器利用了STL库中的stable_sort()函数,避免重复制造车轮。其复杂度是O(N (log N)^2)。在有足够内存时,可以达到O( N log N)。

代码框架

本程序代码为如上结构,分为两个部分:

  • .h
  • .cpp

.h文件:

定义:

  • 宏定义:用于设定存储空间大小
  • 类定义:定义了统计器的类
  • 结构体定义:定义了存放词频统计的结构体

声明

  • 成员函数声明:声明了包括行统计、字符统计、词数统计、词频统计功能的成员函数
  • 功能函数声明:声明了包括字符类型转换、结构体比较的功能函数
  • 异常处理函数声明:声明了三种的异常处理函数

.cpp文件

  • 主函数定义:定义了主函数
  • 成员函数定义:定义了包括行统计、字符统计、词数统计、词频统计功能的成员函数
  • 功能函数定义:定义了包括字符类型转换、结构体比较的功能函数
  • 异常处理函数定义:定义了三种的异常处理函数

频率统计器的实现

下列过程中,从上到下为词频统计的实现大致过程:

  • 打开文件
  • 异常检测
  • 文件流按行读取到字符串数组
  • 特殊符号处理
  • 大写字母处理
  • 行数组单词化
  • 单词筛选
  • 构造红黑树
  • 提取键值对至结构体数组
  • 稳定排序
  • 重构字符流

接口设计与实现

接口设计

  • 设计了一个Counter类,和构造函数。构造函数从外部获取的源文件名和目的文件名,进行文件流操作。
  • 设计了四个具有统计功能的成员函数,通过获取的源、目的文件名,对文件进行读写,统计行数、字符、单词、频率。不在函数中直接输出,或者直接写入文件,而是返回一个整型值,将输出与功能解耦,降低了函数之间的耦合度。
  • 设计了一个Write()函数直接用于文件读写,专门完成该功能。
class Counter
{
private:int Line;
        int Ch;
        int Words;
        string Freq;
        string sfn, dfn;
public:Counter(){}
       Counter(string sfn,string dfn)
       {
           this->sfn = sfn;
           this->dfn = dfn;
           Line = 0;
           Ch = 0;
           Words = 0;
           Freq = "\0";
       }
       int LineCount();
       int CharCount();
       int WordCount();
       string WordFreq();
       void Write();
};
  • 定义了宏,便于更改内存空间使用大小:
#define Linethreshold 5000
#define Charthreshold 50000
#define Wordthreshold 20000
  • 设计了异常处理函数,便于在需要检验之处加入检验功能:
  • 设计了类型转换函数,本统计器多处涉及类型转换,简化了实现:
int DetectFileOpen(ifstream &infile);
int DetectOutfileOpen(ofstream &outfile);
string Conventor(int src);

核心功能词频统计器流程

  • 打开文件
  • 异常检测
  • 文件流按行读取到字符串数组
  • 特殊符号处理:处理非字母和数字字符。
  • 大写字母处理:使用函数将大写字母处理为小写。
  • 行数组单词化:提取单词。
  • 单词筛选:将单词筛选进一个字符串数组。
  • 构造红黑树:将单词和频数构成键值对,
  • 提取键值对至结构体数组:将键值对提取到自设计的结构图数组。
  • 稳定排序:对数组进行稳定排序,保持字典序。
  • 重构字符流:将排序后的前十位输出到字符流中。
string Counter::WordFreq()
  • 特殊符号处理

    for (int i = 0; i
  • 单词提取
    
    for (int i = 0; i> str1[j];
            j++;
        }
    }
  • 单词筛选
    for (int i = 0; i
  • 构造红黑树
    map mymap;
    map::iterator it;

    for (int i = 0; i ::value_type(str1[i], 1));
        }
        else
        {
            mymap[str1[i]]++;
        }
    }   

    it = mymap.begin();

    string temps = "\0";
    stringstream ss;
    int i = 0;
    
    WF a[100];

    for (i = 0; it != mymap.end(); it++, i++)
    {   
        a[i].key = it->first;
        a[i].value = it->second;
    }
    
    stable_sort(&a[0], &a[i+1], cmp);
    for (j = 0; j> temps;
        str[j] = "<" + a[j].key + "" + ">: " + temps;
    }
  • 重构字符流
    for (i = 0; str[i] != "\0"; i++)
    {
        if (i >= 10)break;
        if(str[i][0]=='<')
            result += str[i] + "\n";
        else break;
    }
    //cout <

效果
  • 对一段论文的摘要进行测试:
  • 效果如下

单元测试
  • 单元测试应该在最低的功能/参数上验证程序的正确性。
  • 单元测试必须由最熟悉代码的人(程序的作者)来写。
  • 单元测试过后,机器状态保持不变。
  • 单元测试要快(一个测试运行时间是几秒钟,而不是几分钟)。
  • 单元测试应该产生可重复、一致的结果。
  • 单元测试应该覆盖所有代码路径,包括错误处理路径,为了保证单元测试的代码覆盖率,单元测试必须测试公开的和私有的函数/方法。

基于以上要求,设计了以下单元测试:

序号 测试用例 测试对象 测试结果
1 有空白字符的行 LineCount() 通过
2 存在各种样式的字符 CharCount() 通过
3 数字和单词正确判断 WordCount() 少算一个,更改后通过
4 处理特殊字符 WordCount() 通过
5 处理大写字母 WordFreq() 通过
6 单词种类超过十个 WordFreq() 通过
7 只有字母 WordCount() 迭代器崩溃,更改后通过
8 单词频率是否正确排序 WordFreq() 符合字典序
9 无空行 LineCount() 通过
10 空白文本 LineCount() 输出为0,通过

  • 测试覆盖率,可以看出,覆盖率极高,分析后主要未覆盖部分为异常检测函数,损失部分覆盖率。

以下列出序号1的单元测试代码:

namespace UnitTest1
{       
    TEST_CLASS(UnitTest1)
    {
    public:
        
        TEST_METHOD(TestMethod1)
        {
            // TODO: 在此输入测试代码
            Counter C("F:\\软工\\WordCount\\TEST\\1.txt", "F:\\软工\\WordCount\\TEST\\result.txt");
            int re = C.LineCount();
            Assert::AreEqual(re, 2);

        }

    };
}

性能分析

性能分析图

  • 可以看出,红框内的词频统计功能,占用了最多的CPU资源:

问题发现

  • 从以上两图可以看出,在申请内存空间和返回字符串时,性能开销最大。所以考虑从内存分配入手,优化性能。

解决方案

法一

  • 为了更加灵活高效申请内存空间,设计了宏。
  • 可以方便定义内存空间使用,并且节省CPU消耗。
  • 修改时,只需要修改宏即可。
#define Linethreshold 5000
#define Charthreshold 50000
#define Wordthreshold 20000

法二

  • 当数据量非常大的时候,内存将成为性能瓶颈,提出基于sketch在大数据下的词频统计设计,利用算法Count-min sketch解决内存消耗过大的问题。(具体见下文)

异常处理
  • 设计了三个应用场景的异常处理,包括:
  • 读文件未正常开启:
int DetectFileOpen(ifstream &infile)
{
    if (!infile.is_open())
    {
        cout <<"Cannot open the file, please input right filename!";
        system("pause");
        return 0;
    }
}
  • 写文件未能正常开启:

int DetectOutfileOpen(ofstream &outfile)
{
    if (outfile.is_open())
    {
        cout <<"Cannot open the file!";
        system("pause");
        return 0;
    }
}
  • 输入错误的文件名:

if (argc != 2)
    {
        cout <<"Uncorrect parameters, Please attach .exe and .txt file in order.";
        system("pause");
    }
  • 文件名错误时,报错,并提供解决方案:

PSP表格记录
PSP2.1 header 2 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 35 30
· Estimate ·估计这个任务需要多少时间 15 5
Development 开发 645 1220
· Analysis 需求分析(包括学习新技术) 40 50
· Design Spec · 生成设计文档 40 60
· Design Review · 设计复审 10 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 5 10
· Design · 具体设计 60 120
· Coding · 具体编码 240 600
· Code Review · 代码复审 10 10
· Test ·测试(自我测试,修改代码,提交修改) 240 360
Reporting 报告 245 145
· Test Repor · 测试报告 240 120
· Size Measurement · 计算工作量 5 5
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 20
合计 935 1400

感想
  • 软工作业的量,真是超乎了我的想象,从PSP表中可以看出,我的预估严重不准。而且表中的记录只可能小于实际值,还不包括写博客的时间。这次统计器的实现,不算难,但也走了不少弯路,例如需求分析没有做到位,设计的大局观不足,导致后期代码反复迭代差错,浪费大量时间。其中从《构建之法》中,学到了单元测试要求,很好地帮我解决了设计问题,让我设计出的单元测试更加合理。最有意思的是单元测试还有强大的性能分析工具,在单元测试后,我查出了许多代码中的不足,并加以修改。在性能分析后,我可以准确找到我设计中性能的不足和导致不足相应的代码段,进行优化,应用于之后的程序设计及框架设计,将是一大利器。本次也是代码规范化的一大训练,可以体会一些,在实际开发中的方法和思想,回想到之前实际大型开源项目代码的阅读,对其中代码的规范化、接口的设计体会便更加深刻了。在设计过程中,也偶有灵感扇动,也一并记录下来。

基于sketch在大数据下的词频统计设计

引言

  • 海量数据的下,要对数据中的单词的词频进行统计分析,需要对数据存储、处理,并且维护大量的计数器,将消耗极大资源空间。在本设计中,需要对单词进行分析、处理、稳定排序,最后得出精确的统计结果,而在大数据下,消耗的时间和内存资源是不可接受的。在数据足够多的情况下,要了解单词出现的频率和频率排序,其实没有必要了解词频的精确值,只需提供单词出现的估计值和排序即可。对此,通过在网络测量中相关文献的阅读,提出基于sketch在大数据下词频统计,以达到**节省资源*的目的。

背景

  • 在网络测量中,常使用基于sketch的方法,在高速条件下对网络流的数据进行估计,达到节约资源的目的。在网络中存在各种各样的包,若按精确的分类方法,对每一种包都分配一个计数器储存,虽然测量准确,但存放计数器的空间开销会非常大。故使用哈希的方法,根据哈希值的范围确定的所需的存储空间,各种包根据哈希值再次归类,可以大大减少存储空间*。这样使用哈希来估计流的方法称为Sketch-based方法。
  • 相似的,在大数据下,如果对所有单词都进行准确存储统计,不仅没有必要,而且占用大量内存资源。对此,提出基于sketch在大数据下词频统计,在达到需求的条件下,达到节省资源目的。

解决方案

  • 使用哈希的方法会产生冲突,多个种类的单词哈希到同一个桶内,那么这个桶的计数值就会*偏大,为了减少误差,可以使用count-min sketch
  • Count-min sketch:设置多个哈希函数,开辟一个二维地址空间,包经过不同哈希函数的处理,得到对应的哈希值,而这个哈希值就是sketch(概要)。这些哈希值可能产生冲突,多个种类单词可能有相同的哈希值,根据哈希值来确定单词出现的次数则会偏大,所以设置多个哈希函数,取最小的哈希值,则最接近实际包数据。
  • 得到每个种类包的估计值后,对sketch进行排序,取前十种类的包,即可得到词频统计结果。

总结
  • Sketch是使用哈希来进行估计网络流的一种测量方法,可以减少存储开销,相似地,应用于大数据下的词频统计,可以减少内存开销
  • 可以使用Count-Min Sketch对文本数据进行处理,多个哈希函数的最小哈希值作为词频的估计,实现简单,空间开销较少,

参考文献:
  • SketchVisor: Robust Network Measurement for Software Packet Processing
  • An improved data stream summary: the count-min sketch and its applications
  • https://www.cnblogs.com/fxjwind/p/3289221.html
  • https://blog.csdn.net/lzuacm/article/details/52691450
  • http://www.runoob.com/cplusplus/cpp-files-streams.html
  • https://baike.baidu.com/item/tolower/6389989?fr=aladdin
  • https://baike.baidu.com/item/ctype.h/8497240?fr=aladdin
  • https://blog.csdn.net/sophia1224/article/details/53054698
  • https://bbs.csdn.net/topics/392186748
  • http://www.runoob.com/cplusplus/cpp-stl-tutorial.html
  • https://blog.csdn.net/ajianyingxiaoqinghan/article/details/78540736
  • https://blog.csdn.net/shinetzh/article/details/65660128
  • https://blog.csdn.net/beyongwang/article/details/53074704
  • https://blog.csdn.net/qq_31828515/article/details/52056779
  • http://www.cnblogs.com/xinz/archive/2011/11/20/2255830.html
  • https://www.cnblogs.com/SivilTaram/p/software_pretraining_cpp.html
  • https://www.cnblogs.com/nullllun/p/8214599.html
  • https://www.cnblogs.com/wuchanming/p/4444961.html
  • http://blog.sina.com.cn/s/blog_667cddfc0100wet3.html

推荐阅读
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
author-avatar
Mango-家族
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有