第一次个人编程作业

java余弦相似度实现论文查重——第一次软工个人项目**

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

代码链接

整体流程:

  1. 将文件地址输入到readTxtile类中进读取文件以及去除文本的标点符号,返回String变量data
  2. data进入Analyzer中,进行分词,返回list
  3. list进入Count中,进行getStringFrequency获取词频向量以及getDoubleStrForCosValue进行余弦相似度的公式计算
  4. 三个类由Main调用输出重复率

项目结构:

单元测试

测试覆盖率

单元测试代码
public class MainApplicationTest {

/**
 * 测试 文本为空文本的情况
 */
@Test
public void testForEmpty(){
    try {
        Main.main("src/test/testcase/orig.txt","src/test/testcase/empty.txt","src/test/result/testEmptyResult.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试 输入的对比文本路径参数为错误参数的情况
 */
@Test
public void testForWrongOriginArgument(){
    try {
       Main.main("src/test/testcase/123.txt","src/test/testcase/orig_0.8_add.txt","src/test/result/testAddResult.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试 输出文件路径参数为错误参数的情况
 */
@Test
public void testForWrongOutputArgument(){
    try {
       Main.main("src/test/testcase/orig.txt","src/test/testcase/orig.txt","src/test/result/testAWrongArgumentResult");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试20%文本添加情况:orig_0.8_add.txt
 */
@Test
public void testForAdd(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_add.txt","src/test/result/testAddResult.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试20%文本删除情况:orig_0.8_del.txt
 */
@Test
public void testForDel(){
    try {
        Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_del.txt","src/test/result/testDelResult.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试20%文本乱序情况:orig_0.8_dis_1.txt
 */
@Test
public void testForDis1(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_dis_1.txt","src/test/result/testDis1Result.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试20%文本乱序情况:orig_0.8_dis_3.txt
 */
@Test
public void testForDis3(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_dis_3.txt","src/test/result/testDis3Result.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }

}

/**
 * 测试20%文本乱序情况:orig_0.8_dis_7.txt
 */
@Test
public void testForDis7(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_dis_7.txt","src/test/result/testDis7Result.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试20%文本乱序情况:orig_0.8_dis_10.txt
 */
@Test
public void testForDis10(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_dis_10.txt","src/test/result/testDis10Result.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试20%文本乱序情况:orig_0.8_dis_15.txt
 */
@Test
public void testForDis15(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_dis_15.txt","src/test/result/testDis15Result.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试20%文本格式错乱情况:orig_0.8_mix.txt
 */
@Test
public void testForMix(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_mix.txt","src/test/result/testMixResult.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试20%文本错别字情况:orig_0.8_rep.txt
 */
@Test
public void testForRep(){
    try {
        Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_0.8_rep.txt","src/test/result/testRepResult.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试相同文本:orig.txt
 */
@Test
public void testForSame(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig.txt","src/test/result/testSameResult.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

/**
 * 测试文本的子集文本:orig_sub.txt
 */
@Test
public void testForSub(){
    try {
         Main.main("src/test/testcase/orig.txt","src/test/testcase/orig_sub.txt","src/test/result/testSubResult.txt");
    }
    catch (Exception e) {
        e.printStackTrace();
        // 如果抛出异常,证明测试失败,没有通过,没通过的测试计数在Failures中
        Assert.fail();
    }
}

算法分析

余弦相似度算法

计算公式

关键:余弦相似度 (Cosine Similarity) 通过计算两个向量的夹角余弦值来评估他们的相似度。将向量根据坐标值,绘制到向量空间中,求得他们的夹角,并得出夹角对应的余弦值,此余弦值就可以用来表征这两个向量的相似性。夹角越小,余弦值越接近于1,它们的方向越吻合,则越相似。

设计算法思路:

  1. 对文本进行处理变成纯文本(只含有文字)

  2. 对纯文本进行分词,利用ik分词器

  3. 记录分词后的词频,填入二维数组

  4. 利用数组中的数据进行循环来计算出点积和模,最后带入公式

具体实现

Count进行分次后的字符串的词频计算,用了两次循环分别对两个字符串进行词频计算,每遇到相同的的词语计数变量就自加,遍历完字符串后将当前变量的值放到数组相应位置。

getDoubleStrForCosValue利用Count中得到的字符串词频向量带入余弦相似度公式计算得出结果。

public class Count {

    /**
     * 获取两组字符串的词频向量
     * @param str1List
     * @param str2List
     * @return
     */
    public static int [][] getStringFrequency(List<String> str1List,List<String> str2List){
        Set<String> cnSet = new HashSet<String>();
        cnSet.addAll(str1List);
        cnSet.addAll(str2List);
        int [][] res = new int[2][cnSet.size()];
        Iterator it = cnSet.iterator();
        int i=0;
        while(it.hasNext()){
            String word = it.next().toString();
            int s1 = 0;
            int s2 = 0;
            for(String str : str1List){
                if(word.equals(str)){
                    s1++;
                }
            }
            res[0][i] = s1;
            for(String str : str2List){
                if(word.equals(str)){
                    s2++;
                }
            }
            res[1][i] = s2;
            i++;
        }
        return res;
    }
    /**
     * 获取两组向量的余弦值
     * @param ints
     * @return
     */
    public static float getDoubleStrForCosValue(int [][] ints){
        BigDecimal fzSum = new BigDecimal(0);
        BigDecimal fmSum = new BigDecimal(0);
        BigDecimal seq1SumBigDecimal = new BigDecimal(0);
        BigDecimal seq2SumBigDecimal = new BigDecimal(0);
        int num = ints[0].length;
        for(int i=0;i<num;i++){
            BigDecimal adb = new BigDecimal(ints[0][i]).multiply(new BigDecimal(ints[1][i]));
            fzSum = fzSum.add(adb);
            seq1SumBigDecimal = seq1SumBigDecimal.add(new BigDecimal(Math.pow(ints[0][i],2)));
            seq2SumBigDecimal = seq2SumBigDecimal.add(new BigDecimal(Math.pow(ints[1][i],2)));
        }

        //开方
        double sqrt1 = Math.sqrt(seq1SumBigDecimal.doubleValue());
        double sqrt2 = Math.sqrt(seq2SumBigDecimal.doubleValue());
        //使用BigDecimal保证精确计算浮点数
        fmSum = new BigDecimal(sqrt1).multiply(new BigDecimal(sqrt2));

        return fzSum.divide(fmSum,10,RoundingMode.HALF_UP).floatValue();
    }

JProfile进行性能分析

类的内存消耗

堆内存情况

时间(时间为0.7s,满足要求,耗时较长的部分为获取字符串词频向量的类)

psp表格

PSP 各个阶段 自己预估的时间(分钟) 实际的记录(分钟)
计划: 明确需求和其他因素,估计以下的各个任务需要多少时间 30 60
开发 (包括下面 8 项子任务) 420 650
需求分析 (包括学习新技术、新工具的时间) 60 120
生成设计文档 30 50
设计复审 30 20
代码规范 (为目前的开发制定或选择合适的规范) 30 30
具体设计 60 100
具体编码 120 200
代码复审 30 20
测试(自我测试,修改代码,提交修改) 60 90
报告 180 260
测试报告 60 80
计算工作量 30 30
事后总结, 并提出过程改进计划 90 150
合计 600 860

总结

  1. 第一次独立完成的个人项目,在网上查了很多资料,学习到了很多新的知识,第一次使用ik分词器,代码实现了余弦相似度的计算
  2. 需求分析做的不够好,由于是第一次做个人项目,在分析这一块做的非常差,在看到项目需求之前根本不知道从何下手,查了很多资料之后才明白
  3. 在psp中也体现出来预想的与实际确实差距很大,需要积累更多的项目经验
  4. 不足的地方太多了,学无止境,继续学习
posted @ 2020-09-25 00:02  悦夫子  阅读(162)  评论(0编辑  收藏  举报