程序相似性简易判断

第三次实验报告

程序语言:python
姓名: unicorn
学号: 12345678910
日期:2023/4/8


一、 问题重述

  给定两个程序,如何判断他们的相似性?


二、 问题分析

  先假设程序为C语言,不然题目太简洁了无从下手。C语言是比较基础的语言,我对C语言也比较了解,方便操作。然后还要假设比较的两个代码都是正确的,如果出现语法错误就没有规律可循了。
  接着就是对源代码的预处理,要让文本分析更方便,首先就是去除空格、空行和一些无用的符号,减少字符串总长度,降低算法对无用字符的处理时间,然后就是去除注释和头文件,减小无关因素的干扰。
  相似性主要由两个技术分析:属性计数技术和结构度量技术。这里主要采取结构度量技术中的最长公共子序列辅助属性计数技术的欧几里德距离法(稍微修改了一下)进行判断。


三、代码

代码如下:

import math

var = ["int", "float", "double", "char"]  # 常见数据类型


def whether_variable(a):  # 返回true还是变量名 返回false应该不是变量名了
    return a.isalnum() or a == '_'


def dis(a, b):  # 将单个维度的值转换为一个0~1之间的值方便两次比较
    mid = (a + b) / 2
    return math.fabs(a - mid) * math.fabs(b - mid) / (mid * mid)


def preprocess(src_path):
    result = {}
    variable = []
    dst_path = 'C:\\Users\\Lenovo\\Desktop\\' + src_path + "_new.txt"
    src_path = 'C:\\Users\\Lenovo\\Desktop\\' + src_path + ".txt"
    with open(src_path, encoding='utf-8') as file:
        content = file.readlines()
        f2 = open(dst_path, 'w').close()  # 清空内容
        f2 = open(dst_path, "a+", encoding='utf-8')  # 目标文件用来存储
        flag = 0  # 标记位,标记是否进入多行注释/* */
        variable_total = 0  # 代码中所含变量数
        for kk in range(0, len(content)):
            for i in range(0, len(var)):
                if content[kk].count(var[i]):
                    place = content[kk].find(var[i])
                    if not whether_variable(content[kk][place - 1]) and \
                            not whether_variable(content[kk][place + len(var[i])]):  
                            # 说明是变量声明且不是for循环变量
                        variable_total += content[kk].count(",") + 1
                        break
        variable_total -= 1  # 减去main的变量
        for kk in range(0, len(content)):
            content[kk] = content[kk].replace(" ", "")  # 去除空格
            length = len(content[kk])  # 每行字符数
            for i in range(length):
                # 单行注释//
                if flag == 0 and content[kk][i] == '/' and content[kk][i + 1] == '/':
                    f2.write("\n")
                    break
                # 多行注释/* .....  */
                elif flag == 0 and content[kk][i] == '/' and content[kk][i + 1] == '*':
                    flag = 1
                elif flag == 1 and content[kk][i] == '*' and content[kk][i + 1] == '/':
                    flag = 0
                    break
                elif flag == 1:
                    continue
                # 空行 \n
                elif content[kk][0] == '\n':
                    continue
                # 头文件和宏定义
                elif content[kk][0] == '#':
                    continue
                elif flag == 0:
                    f2.write(content[kk][i])
        f2.close()
        with open(dst_path, "r", encoding='utf-8') as f2:  # 目标文件用来存储
            a = f2.read()
            result["code_rows"] = a.count("\n")
            a = a.replace("\n", "")  # 去除换行
        result["string"] = a
        result["variable_total"] = variable_total
        return result


def lcs(a, b):
    maxn = 5005
    f = [[0] * maxn for _ in range(2)]
    g = [[0] * maxn for _ in range(2)]
    n = len(a)
    m = len(b)
    cur = 0
    for i in range(0, m):
        g[cur][i] = 1
    for i in range(0, n):
        cur ^= 1
        g[cur][0] = 1
        for j in range(1, m):
            g[cur][j] = 0
            f[cur][j] = max(f[1 - cur][j], f[cur][j - 1])
            if a[i] == b[j]:
                f[cur][j] = max(f[cur][j], f[1 - cur][j - 1] + 1)
                g[cur][j] = g[1 - cur][j - 1]
                if f[cur][j] == f[cur][j - 1]:
                    g[cur][j] += g[cur][j - 1]
                if f[cur][j] == f[1 - cur][j]:
                    g[cur][j] += g[1 - cur][j]
            else:
                if f[cur][j] == f[1 - cur][j]:
                    g[cur][j] += g[1 - cur][j]
                if f[cur][j] == f[cur][j - 1]:
                    g[cur][j] += g[cur][j - 1]
                if f[1 - cur][j - 1] == f[cur][j]:
                    g[cur][j] -= g[1 - cur][j - 1]
    if a == b:
        f[cur][m - 1] += 1
    return f[cur][m - 1], g[cur][m - 1]


a = preprocess("program1")  # 第一个文档名字
b = preprocess("program4")  # 第二个文档名字
m, n = lcs(a["string"], b["string"])
# 类似于相似度率,越靠近1越相似,越靠近0越不相似
print(m/min(len(a["string"]), len(b["string"])))  
print(n)  # 最长公共子序列
distance = math.sqrt(dis(a["code_rows"], b["code_rows"]) +
                     dis(len(a["string"]), len(b["string"])) +
                     dis(a["variable_total"], b["variable_total"]))
print(distance)
print(a)  # 打印a的字典方便检查
print(b)  # 打印b的字典方便检查







四、 实验结果

输出大概如图
①简易修改后判断相似性
源代码:
源代码
抄袭代码:
抄袭代码

抄袭代码的简易改动输出如下
抄袭代码输出
②不同代码测试:
第一个文档同上图抄袭代码,第二个文档如下:
判断
输出:
输出


五、 遇到的一些问题

  文本预处理时我也是自学了关于python文本操作的相关函数,不过这个倒是好上手,但是python的字符串类型不能直接替换确实困扰了一段时间。最后改用直接修改在另一个文档来解决问题,源文档只用于读取源代码,目标文档用来存储处理后的代码(即去除注释、空行和头文件的代码)。并可以通过读取目标文档来获得的处理后的代码,间接拒绝了不能修改字符串的问题。
  计算变量时注意到int main()可能会被误判,于是直接在结果后面减1。
  还有一个很多意思的就是变量名我是根据定义多少变量的时候来算,于是就要搜索int等关键字,但是刚好printf里面也有int所以会导致错误结果,这里多加了一个whether_variable函数进行判断前后字符,但是也有一个不好的地方就是要保留空格方便判断,所以可能会增加一些遍历量,不过这个影响比较小。空格留到后面再行去除。


六、 总结

  可能对于多次函数调用,以及高级的代码修改方法效果不佳,但可识别基础的通过增加代码注释和空行空白等方法进行修改抄袭代码。
  还有很多方面由于时间不足没有来得及完善:变量不具备识别出现次数功能,变量名影响结构度量计数问题,关键字与操作符、数组和函数计数问题。
  但是本代码还是采取属性和计数两种方法比较简单的比较了代码的相似性,对于一般的相似性比较实用度高。更加完整或者效果更佳的代码相似度检验可以看一下下列参考资料的1和2应该是学术界比较先进的算法。
  本次实验也是更加深刻的了解了最长公共子序列求解思路,以及掌握一些python关于文件读写操作和字符串处理的方法。

相关参考信息

[1]卫军超,耿楠.程序代码相似度检测技术的研究与实现[J].电脑知识与技术,2017,13(05):39-40.DOI:10.14004/j.cnki.ckt.2017.0578.(相似度基本方法思路)
[2]肖丽, 校景中. 基于RKRGST的算法分析[J]. 西南民族大学学报:自然科学版, 2010(5):5.
[3]kiddingme12138,www.luogu.com.cn.2019-08-12(最长公共子序列代码思路)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值