动态规划计算文本相似度项目—第一次个人编程作业(软件工程)

github链接

软件工程 https://edu.cnblogs.com/campus/gdgy/informationsecurity1812
作业要求 https://edu.cnblogs.com/campus/gdgy/informationsecurity1812/homework/11155
作业目标 论文查重算法设计+单元测试+JProfiler+PSP表格+Git管理

整体流程

  1. 程序入口MainApplication里面传入:需要检验重复的文章,要对比的文章,以及输出的文章。
  2. IOUtil进行io的读写操作,获得文本
  3. TextUtil过滤去html标签,同时分句,获得一个分句的list
  4. 两边要检测的文本进行m*n级别的比照
  5. 每句的比照重复使用EasyCompared中的compaired方法,利用动态规划getDistance获得两个句子之间最少的移动距离。最后根据移动的距离以及两个句子的长度,来计算这两个句子是否相似
  6. 最后根据相似的句子的数目,和总句子的数目来计算相似度。
  7. 最后输出将相似度写入文件

项目结构

主要的算法

//动态转移方程
d[i][j] = getMin(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1]+ temp);


我们这个表是对比"我喜欢吃牛扒"和"你喜欢打牛牛"
可以看到(欢,欢)这个坐标是1,这个1表示的是,"我喜欢"和"你喜欢"之间的距离间隔着1,这两个词之间最少经过1次字符的变换就能得到另一个词。
那这个1怎么来的呢,我们可以试想一下,"你喜欢"和"我喜欢"的上一步,到底是怎么经过最少次变换得来的,可以找
"我喜"->"你喜欢"
"我喜欢"->"你喜"
"我喜"->"你喜"
分别对应的坐标是d[i - 1][j],d[i][j - 1],d[i - 1][j - 1]
只要我们知道,这三者的中最小的(上一次+本次变换步数)就可以得到到底变换了多少步骤。
最后就可以得出这条动态转移方程。
d[i][j] = getMin(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1]+ temp);

距离得到了,那怎么判断这两个句子是否相似呢,这里我就设计了一个值,k

|s1-s2|是s1和s2之间的长度差
d是s1和s2的变换距离差。
根据算出来的k值再判断两个k是否相似。
k越小越相似!
一开始想设计d/s1,但是这样一想好像有点不对劲,因为"我"和"我喜欢吃蒜香鸡"这两个句子算的话d/s1就会是一个巨大的数据
于是假如|s1-s2|消除一下句子过长的影响。

这条式子其实可以再优化一下,不过概率论没学好,以后继续加油。

最后我们就可以得到,原文中存在重复句子的个数,
用原文/疑似原文就可以得到重复度。

时间复杂度分析

时间复杂大概是O(n2),首先进行m*n级别的句子匹配,每对句子之间要进行a2级别的匹配。但是总体来说,基本上每个字都要经历一次句子长度*疑似文本长度的运算。

代码测试的覆盖度

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class MainApplicationTest {
    @BeforeClass
    public static void beforeTest(){
        System.out.println("junit单元测试开始");
    }

    @AfterClass
    public static void afterTest(){
        System.out.println("junit单元测试结束");
    }

    /**
     * 测试 正常的测试
     */
    @Test
    public void testForAdd(){
        MainApplication.run("C:\homework\orig.txt","C:\homework\orig_0.8_add.txt","C:\homework\happy.txt");
    }

    /**
     * 测试 删除orig_0.8_del.txt
     */
    @Test
    public void testForDel(){
        MainApplication.run("C:\homework\orig.txt","C:\homework\orig_0.8_del.txt","C:\homework\happy.txt");
    }

    /**
     * 测试 分割orig_0.8_dis_1.txt
     */
    @Test
    public void testForDis1(){
        MainApplication.run("C:\homework\orig.txt","C:\homework\orig_0.8_dis_1.txt","C:\homework\happy.txt");
    }

    /**
     * 测试 分割orig_0.8_dis_10.txt
     */
    @Test
    public void testForDis10(){
        MainApplication.run("C:\homework\orig.txt","C:\homework\orig_0.8_dis_10.txt","C:\homework\happy.txt");
    }

    /**
     * 测试 分割orig_0.8_dis_15.txt
     */
    @Test
    public void testForDis15(){
        MainApplication.run("C:\homework\orig.txt","C:\homework\orig_0.8_dis_15.txt","C:\homework\happy.txt");
    }

    /**
     * 测试 传入的文件路径错误
     */
    @Test
    public void testFororigpath(){
        MainApplication.run("don't run","C:\homework\orig_0.8_dis_15.txt","C:\homework\happy.txt");
    }


}

异常的处理


文件名不对的时候的报错

JProfile进行性能测试

内存的使用情况

cpu的运行时间

耗时最长的地方


是getdistance方法,由于必须进行n^2级别的运算才能算完,而且已经是优化过后的动态规划了,所以改不了-3-。

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 20
· Estimate · 估计这个任务需要多少时间 180 250
Development 开发 30 35
· Analysis · 需求分析 (包括学习新技术) 15 30
· Design Spec · 生成设计文档 25 25
· Design Review · 设计复审 15 15
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 30
· Design · 具体设计 25 25
· Coding · 具体编码 20 20
· Code Review · 代码复审 20 20
· Test · 测试(自我测试,修改代码,提交修改) 30 35
Reporting 报告 30 40
· Test Repor · 测试报告 30 40
· Size Measurement · 计算工作量 15 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 20
· 合计 485 625

总结

1.这次个人项目收获了挺多东西,尤其是第一次借助到测试这个工作,以及这些工具jProfile,还有使用junit进行单元测试
2.学到了不少算法,虽然写上去的还是自己想的,这个算法有优点也有缺点,优点就是,思路比较简单,不用依赖外部的库,而且是靠句子来分割的,判断重复的力度大一点,缺点也是很明显的,没有句意分析,同义词,以及时间复杂度过高的问题,看了不少大佬写的太优秀了。
3.最后发现自己还有很多不足的地方,以后要加把劲吼。

原文地址:https://www.cnblogs.com/chiguoguo/p/13722071.html