一、问题描述

对于两个C++语言的源程序代码,用哈希表的方法分别统计两个程序中使用C++语言关键字的情况,并最终按定量的计算结果,得出两份程序的相似性。

二、需求分析

建立C++语言关键字的哈希表,统计在每个源程序中C++关键字出现的频度, 得到两个向量X1和X2,通过计算向量X1和X2的相对距离来判断两个源程序的相似性。

例如:

    关键字       Void  Int  For  Char  if  else  while  do  break  class
程序1关键字频度    4    3      0    4    3    0    7      0    0      2
程序2关键字频度    4    2      0    5    4    0    5      2    0      1 X1=[4,3,0,4,3,0,7,0,0,2] X2=[4,2,0,5,4,0,5,2,0,1]

设s是向量X1和X2的相对距离,s=sqrt(∑(x1[i]-x2[i])2),当X1=X2时,s=0, 反映出可能是同一个程序;s值越大,则两个程序的差别可能也越大。

测试数据
选择若干组编译和运行都无误的C++程序,程序之间有相近的和差别大的,用上述方法求s, 对比两个程序的相似性。

选作内容
建立源代码用户标识符表,比较两个源代码用户标识符出现的频度,综合关键字频度和用户标识符频度判断两个程序的相似性。

三、概要设计

为了实现上述功能,需要使用哈希表,Python中已经有基于哈希表实现的字典数据结构,可以直接使用,但是为了锻炼自己的编程能力,还是决定手动实现一个哈希表。

哈希表是根据关键字直接进行访问的数据结构,是一组元素的集合,元素的存储方式使我们可以在O(1)的时间内找到它们。哈希表的每个位置(通常称为槽)可以容纳一个元素,通过哈希函数建立了关键字和存储地址之间的一种直接映射关系,插入数据时,如果为空则直接插入,发生哈希碰撞时,如果key相等,并且key所对应的位置上有数据,则直接进行替换,如果key不相等,则利用线性探测的开放寻址法,找到下一个槽位并插入。

为了更加深刻的理解CPython字典对象的内部构造和实现,我去翻阅了CPython的内核源码,并打算以不同版本的CPython字典对象的C文件为测试数据,计算并分析其差异。

在Python3.6之前的版本,针对以稀疏的哈希表存储的字典对象是非常不友好的,如下图是一个指向Python内部字典对象的入口指针:

基于这种形式,如果应用场景下有较多的字典对象时,会浪费很多的内存空间,为了用更紧凑的方式来实现哈希表,Python3.6以后的版本采用了将哈希索引和真正的键值对分开存放的方式:

这么做的好处是空间利用率得到了较大的提升,以64位操作系统为例,每个指针的长度为8个字节,原本需要838=192个字节,现在变成了833+1*8=80个字节,节省了58%左右的内存空间。

本次实验以Python 2.7、3.6、3.7三个版本的dictobject.c文件为输入,处理文件过程包括筛除注释和空行等,然后以正则表达式切分单词并做词频统计,之后再根据词频向量计算不同文件之间的欧氏距离和余弦相似度,最后输出实验结果。

四、详细设计

1.哈希表

Python中的字典是一种关联数据类型,可以在其中存储键-值数据,通过键可以查找关联的数据值,这个想法也被称为map,抽象数据类型的定义如下:

  • Map()创建一个新的空映射。它返回一个空的映射集合。
  • put(key,val)向映射添加新的键值对。如果键已经在映射中,则用新值替换旧值。
  • get(key)给定一个键,返回存储在映射中的值,否则返回None
  • del使用del map[key]格式的语句从映射中删除键值对。
  • len()返回存储在映射中的键值对的数目。
  • in如果给定键在映射中,则返回True,否则返回False。

为了减少哈希冲突,哈希表的长度最好选择质数。假设哈希表的长度设置为15,则3的倍数或5的倍数的整数将分别散列为3和5的索引,换句话说,每个与长度共享一个公因子的整数都将被散列到该因子倍数的索引中。对于非随机数据,素数长度的哈希表将产生最广泛的整数分布到索引。因此,选择将哈希表的长度设置为质数将大大减少冲突的发生。

2.读取+处理文件

测试文件为Python 2.7、3.6、3.7三个版本的dictobject.c文件,使用Python读取文件比较简单,复杂的是对文件内容的处理,正规的C语言文件包含大量的注释,因此首先就要过滤到文件中的注释。

C语言文件的注释包括单行注释、多行注释和行内注释,并且还要去除空行。首先使用open()打开文件并返回一个file object,针对每一行单独进行处理。如果当前行是空行、以//开头或者以/*开头以*/结尾,则直接跳过。对于多行注释的判断可以借助于一个标志位isNotes,如果isNotes为False并且当前行以/*开头,则说明当前行是多行注释的开始行,设置isNotes为True,直到当前行以*/结尾,说明当前行是多行注释的结束行,设置isNotes为False。经过以上两次过滤之后,接下来要处理的就是行内注释,可以针对行内的///*进行索引和切片去除行内注释。

接下来具体处理C语言程序的每一行代码,首先通过正则表达式pattern = r'[\s,\.?;!:"]+'切分单词,切分符包括空白符号、英文逗号、英文句号、英文问号、英文感叹号、英文冒号、英文分号,最后的加号表示如果这些符号是连续挨着的则当成一个分隔符切分,切分后针对每个单词调用get函数获取值,然后在哈希表中查找是否存在该单词是否为C语言关键字,如果存在则该关键字则对应的频度加1。

3.计算欧式距离和余弦相似度

欧式距离也称欧几里得距离,是最常见的距离度量,衡量的是多维空间中两个点之间的绝对距离。也可以理解为:m维空间中两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离)。在二维和三维空间中的欧氏距离就是两点之间的实际距离,公式为:s=sqrt(∑(x1[i]-x2[i])2)。

对于如何计算两个向量的相似程度问题,可以把它们想象成空间中的两个线段,都是从原点(0, 0, …)出发,指向不同的方向,两条线段之间形成一个夹角,如果夹角为0度,意味着方向相同、线段重合;如果夹角为90度,意味着方向形成直角;如果夹角为180度,意味着方向正好相反。因此,可以通过夹角的大小来判断向量的相似程度,夹角越小,就代表越相似。

两个线段A和B的夹角θ的余弦:

余弦值的范围在[-1,1]之间,值越趋近于1,代表两个向量的方向越接近;越趋近于-1,他们的方向越相反;接近于0,表示两个向量近乎于正交。一般情况下,相似度都是归一化到[0,1]区间内,因此余弦相似度表示为cosineSIM = 0.5cosθ + 0.5

五、编码实现

1.哈希表

我使用两个列表来创建一个实现映射抽象数据类型的哈希表类,一个名为slot的列表保存密钥,另一个名为data的并行列表保存数据值。

class HashTable:def __init__(self, size):self.size = sizeself.slot = [None] * self.sizeself.data = [None] * self.size

哈希函数实现了简单的余数方法,为了处理基于字符的元素(字符串),取其每个字母的序数值的和进行取余,通过线性探测再哈希函数解决碰撞问题。

    def put(self, key, data):hashValue = self.hashFunction(key, len(self.slot))# 如果slot内是empty,就存进去if self.slot[hashValue] is None:self.slot[hashValue] = keyself.data[hashValue] = dataelse:# 如果已有值等于key,更新dataif self.slot[hashValue] == key:self.data[hashValue] = dataelse:# 如果slot不等于key,找下一个为None的地方nextSlot = self.rehash(hashValue, len(self.slot))while self.slot[nextSlot] is not None and self.slot[nextSlot] != key:nextSlot = self.rehash(nextSlot, len(self.slot))if self.slot[nextSlot] is None:self.slot[nextSlot] = keyself.data[nextSlot] = dataelse:self.data[nextSlot] = data@staticmethoddef hashFunction(key, size):return sum([ord(ch) for ch in key]) % size@staticmethoddef rehash(oldHash, size):return (oldHash + 3) % size

同样,get函数首先计算初始哈希值,如果该值不在初始槽中,则使用rehash来定位下一个可能的位置。

    def get(self, key):startSlot = self.hashFunction(key, len(self.slot))data, stop, found, position = None, False, False, startSlotwhile self.slot[position] is not None and not found and not stop:if self.slot[position] == key:found = Truedata = self.data[position]else:position = self.rehash(position, len(self.slot))# 如果回到开始位置说明没找到if position == startSlot:stop = Truereturn data

HashTable类提供了额外的字典功能,通过重载__getitem__和__setitem__方法以允许使用[]进行访问,这意味着创建的哈希表可以通过熟悉的索引操作符使用。

    def __getitem__(self, key):return self.get(key)def __setitem__(self, key, data):self.put(key, data)

2.读取+处理文件

读取三个版本的dictobject.c文件并过滤空行和注释。

def readFile(path):file, isNotes = [], Falsewith open(path, "r") as fp:for line in fp.readlines():line = line.strip()# 过滤C语言文件中的空行、单行注释、多行注释和行内注释if isNotes and line.endswith("*/"):isNotes = Falsecontinueif not line or line.startswith("//") or (line.startswith("/*") and line.endswith("*/")) or isNotes:continueif not isNotes and line.startswith("/*"):isNotes = TruecontinuenoteIndex = line.find("//") if line.find("/*") == -1 else line.find("/*")if noteIndex != -1:line = line[:noteIndex]file.append(line)return filedef readFiles():return readFile("dictobject-2.7.c"), readFile("dictobject-3.6.c"), readFile("dictobject-3.7.c")file1, file2, file3 = readFiles()

通过正则表达式处理文件并统计关键字词频。

def processFile(file, table: HashTable):for line in file:pattern = r'[\s,\.?;!:"]+'words = re.split(pattern, line)for word in words:if table.get(word) is not None:table.put(word, table.get(word) + 1)

3.计算欧式距离和余弦相似度

欧几里得距离计算公式:s=sqrt(∑(x1[i]-x2[i])2)。

def computeEuclideanDistance(array1, array2):return math.sqrt(sum([(array1[i] - array2[i]) ** 2 for i in range(len(array1))]))

根据余弦定理计算余弦相似度:

def computeCosineSimilarity(x, y):zeroList = [0] * len(x)if x == zeroList or y == zeroList:return float(1) if x == y else float(0)dotProduct, squareSumX, squareSumY = 0, 0, 0for i in range(len(x)):dotProduct += x[i] * y[i]squareSumX += x[i] * x[i]squareSumY += y[i] * y[i]cos = dotProduct / (math.sqrt(squareSumX) * math.sqrt(squareSumY))return 0.5 * cos + 0.5

六、实验结果与分析

+----------+-----------------------+-----------------------+-----------------------+
| Keyword  | File 1 Word Frequency | File 2 Word Frequency | File 3 Word Frequency |
+----------+-----------------------+-----------------------+-----------------------+
|   auto   |           0           |           0           |           0           |
|  static  |           97          |          101          |          102          |
| register |           54          |           1           |           1           |
|  extern  |           3           |           0           |           0           |
|   char   |           4           |           4           |           4           |
|  double  |           0           |           0           |           0           |
|   enum   |           0           |           0           |           0           |
|  float   |           0           |           0           |           0           |
|   int    |           56          |           68          |           68          |
|   long   |           24          |           0           |           0           |
|  short   |           0           |           0           |           0           |
|  signed  |           0           |           0           |           0           |
|  struct  |           2           |           4           |           4           |
|  union   |           0           |           0           |           0           |
| unsigned |           0           |           0           |           0           |
|   void   |           14          |           16          |           15          |
|   for    |           23          |           39          |           40          |
|    do    |           3           |           1           |           1           |
|  while   |           13          |           19          |           16          |
|  break   |           8           |           9           |           9           |
| continue |           4           |           5           |           3           |
|    if    |          293          |          420          |          385          |
|   else   |           36          |           67          |           64          |
|   goto   |           33          |           32          |           31          |
|  switch  |           0           |           0           |           0           |
|   case   |           7           |           7           |           7           |
| default  |           0           |           0           |           0           |
|  const   |           3           |           5           |           6           |
|  sizeof  |           0           |           0           |           0           |
| typedef  |           2           |           1           |           1           |
| volatile |           0           |           0           |           0           |
|  return  |          246          |          321          |          307          |
+----------+-----------------------+-----------------------+-----------------------+
dictobject-2.7.c和dictobject-3.6.c的欧式距离:163.03680566056244,余弦相似度为:0.9929622255286137
dictobject-3.6.c和dictobject-3.7.c的欧式距离: 38.05259518088089,余弦相似度为:0.9998047476290259
dictobject-2.7.c和dictobject-3.7.c的欧式距离:129.82680770934792,余弦相似度为:0.9936477207981805Process finished with exit code 0


分析:实验结果显示,虽然由于文件比较大,导致欧氏距离和余弦相似度比较大,Python2.7版本的dictobject.c文件和Python3.6版本的dictobject.c文件欧氏距离最大,相似度也相对较小,Python3.6版本的dictobject.c文件和Python3.7版本的dictobject.c文件欧氏距离最小,相似度也最大。实际情况下,Python2.7和Python3.6差了一个大版本,而Python3.6和Python3.7版本差了一个小版本,符合实验结果。

七、总结

哈希表,就是一种结构数据,它以牺牲空间为代价节约时间,哈希表的运用感觉还是挺简单的,不过对于具体的题目,以什么值为下标,储存其值,如何去查找还需要思考,如果单方面的看哈希表是简单的,就是在于如何放在题目中运用。

总结下来,哈希散列的两大问题:一个就是散列策略,另一个就是解决冲突。而且不难发现,哈希表的空间越大,冲突的频率就越低,这样时间复杂度就越接近O(1)。所以我们经常将哈希表的大小稍微设置得大一些,用空间来换时间也是挺值得的。

通过本次课程设计,让我更进一步的体会到了数据结构与算法的重要性,学会从问题入手,分析和研究计算机加工的数据结构特性,为应用数据选择适当的逻辑结构、存储结构及其相应的操作算法,并初步掌握算法的性能分析技术。

源程序的相似性分析 —— 基于Python实现哈希表相关推荐

  1. 七十五、Python | Leetcode哈希表系列

    @Author:Runsen @Date:2020/7/3 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  2. python leetcode_七十五、Python | Leetcode哈希表系列

    @Author:Runsen @Date:2020/7/3 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  3. python实现哈希表

    # python 实现哈希表class HashTable:"""哈希函数的构造解决冲突"""def __init__(self, sour ...

  4. python建立哈希表_python实现哈希表

    复制代码 代码如下: #! /usr/bin/env python #coding=utf-8 #实现哈希表(线性地址再散列) def ChangeKey(key,m,di): key01=(key+ ...

  5. 短文本分析----基于python的TF-IDF特征词标签自动化提取

    绪论 最近做课题,需要分析短文本的标签,在短时间内学习了自然语言处理,社会标签推荐等非常时髦的技术.我们的需求非常类似于从大量短文本中获取关键词(融合社会标签和时间属性)进行用户画像.这一切的基础就是 ...

  6. python文献检索工具与技巧答案_短文本分析----基于python的TF-IDF特征词标签自动化提取...

    绪论 最近做课题,需要分析短文本的标签,在短时间内学习了自然语言处理,社会标签推荐等非常时髦的技术.我们的需求非常类似于从大量短文本中获取关键词(融合社会标签和时间属性)进行用户画像.这一切的基础就是 ...

  7. 词云分析——基于Python对天猫商品评论进行词云分析

    文章目录 0 引言 1 准备工作 2 主程序 3 分析与改进 4 可能出现的报错及解决方案 0 引言 什么是词云分析? 词云图,也叫文字云,是对文本中出现频率较高的"关键词"予以视 ...

  8. Sen+MK长时间序列趋势性分析----基于python的代码实现

    sen+mk python实现代码免费共享-----赶紧收藏吧 python开源社区公布了进行sen+mk趋势性检验的官方包,有关该官方包的主要内容详见:https://github.com/Code ...

  9. 生日悖论分析基于python

    题目:生日悖论指如果一个房间里有23人或以上,那么至少有两个人生日相同的概率大于50%.编写程序,输出在不同随机样本数量下,23个人中至少有两个人生日相同的概率. 基本思想:首先建立一个列表,遍历一年 ...

最新文章

  1. Kali Linux 2020.1快速修改root用户密码
  2. 解析软件测试需要变革的因素
  3. php性能优化分析工具XDebug 大型网站调试工具
  4. 详解Ubuntu Server下启动/停止/重启MySQL数据库的三种方式(ubuntu 16.04)
  5. IIS支持下载.config后缀名的文件
  6. (12)Verilog HDL变量:reg型
  7. 从C语言到C++的进阶之C++的非类新特性(篇三)
  8. 玩转Android Camera开发(一):Surfaceview预览Camera,基础拍照功能完整demo
  9. c语言三角函数乘法怎么表示,这个图里的三角函数 要怎么用c语言打出来
  10. ISSCC 2017论文导读 Session 14 Deep Learning Processors,A 2.9TOPS/W Deep Convolutional Neural Network
  11. Unity分屏之使用TUIO实现互动投影
  12. 2022年电脑杀毒软件PK
  13. 推进BI国产化替代,永洪科技新产品性能提升200%
  14. [bzoj 4833]最小公倍佩尔数
  15. MATLAB常用命令总结
  16. log4net进阶手札(三):保存日志到oracle中
  17. 特别调查:嘿!问你为什么非要过圣诞?
  18. 关于跨网闸数据同步的方式调研Java实现ModbusTCP通信
  19. redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refu
  20. Python学习第四篇:利用python抓取英语单词

热门文章

  1. 洛谷:P1042 [NOIP2003 普及组] 乒乓球 C++详解
  2. Legacy BIOS MBR 安装黑苹果 High sierra
  3. K12在线教育持续升温,教育需线上线下相结合!
  4. 移动硬盘接android手机吗,笔点说:智能手机可以直接连接移动硬盘读取数据吗?...
  5. ps 改变图层纯色与渐变色
  6. 生僻字用计算机怎么弹歌曲,抖音生僻字计算器乐谱 计算器弹歌曲音乐乐谱大全...
  7. 微信公众号配置网页授权域名报错:无法访问xxx指向的web服务器或虚拟主机的目录
  8. 音视频dsp中对音频的处理
  9. 使用matlab绘制弧线,MATLAB求解叶型中弧线
  10. python语言单词_python 单词