当前位置: 首页 > article >正文

使用求2个字符串最短编辑距离动态规划算法实现 git diff 算法 java 实现

测试类 MyDiffTest.java:
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;


public class MyDiffTest {

   
     private static String path = "\\xxx\\";

    private static List<String> lines_v1 = readFile2List( path + "DemoClass1.java" );
    private static List<String> lines_v2 = readFile2List( path + "DemoClass2.java" );

    public static void main(String[] args) {
        /*lines_v1 = new ArrayList<>();
        lines_v2 = new ArrayList<>();
        lines_v1.add( "m" );
        lines_v1.add( "o" );
        lines_v1.add( "t" );
        lines_v1.add( "h" );
        lines_v1.add( "e" );
        lines_v1.add( "r" );

        lines_v2.add( "m" );
        lines_v2.add( "o" );
        lines_v2.add( "n" );
        lines_v2.add( "s" );
        lines_v2.add( "t" );
        lines_v2.add( "e" );
        lines_v2.add( "r" );*/

        int[][] dp =  calculateMinimumTransferWay(lines_v1, lines_v2);

        int index1 = lines_v1.size() - 1;
        int index2 = lines_v2.size() - 1;
        System.out.println( "最短编辑距离:" +  dp[index1][index2] );

        List<String> result = new ArrayList<>();
        while ( index1 >= 0 && index2 >= 0 ){
            String line_v1 = lines_v1.get(index1);
            String line_v2 = lines_v2.get(index2);

            if( line_v1.equals( line_v2 ) ){
                // v1:...a
                // v2:...a
                // 此时,v1 和 v2 的当前行相同,原封不懂的输出,表示没有改动
                result.add( "  " + line_v1 );
                index1--;
                index2--;
            }else {
                // v1:...a
                // v2:...b
                // 此时,v1版本的当前行和 v2版本的当前行不相同,所以v1转化为v2过程中,对 行a、行b的操作有3种可能:新增行b、删除行a

                // v1:...a
                // v2: ...  b
                // 如果此时的编辑距离是最小的,则v2版本需要新增 行b
                int sed1 = dp[index1][index2 - 1] + 1; // ps:sed 是 shortest edit distance 的缩写

                // v1: ...  a
                // v2:...b
                // 如果此时的编辑距离是最小的,则v2版本需要删除 行a
                int sed2 = dp[ index1 - 1 ][ index2 ] + 1;

                // v1: ...  a
                // v2: ...  b
                // 如果此时的编辑路基是最小的,则v2版本需要将 删除行a+新增行b( 一共2步操作 )
                int sed3 = dp[ index1 - 1 ][ index2 - 1 ] + 2;

                int sed = Math.min(Math.min(sed1, sed2), sed3);
                if( sed1 == sed ){
                    result.add( "+ " + line_v2 );
                    index2--;
                }else if( sed2 == sed ){
                    result.add( "- " + line_v1 );
                    index1--;
                }else if( sed3 == sed ){
                    result.add( "+ " + line_v2 );
                    result.add( "- " + line_v1 );
                    index1--;
                    index2--;
                }
            }
        }
        while ( index1 >= 0 ){
            // v1 还剩下的一些 "首行们" 都是需要被删除的行,因为v2版本没有
            result.add( "- " + lines_v1.get( index1 ) );
            index1--;
        }

        while ( index2 >= 0 ){
            // v2 还剩下的一些 "首行们" 都是需要被添加的行,因为v1版本没有
            result.add( "+ " + lines_v2.get( index2 ) );
            index2--;
        }

        for (int i = ( result.size() - 1 ); i >= 0;i-- ) {
            System.out.println( result.get( i ) );
        }
    }

    private static List<String> readFile2List( String filePath ){
        BufferedReader reader = null;
        try {
            List<String> lines = new ArrayList<>();
            reader = new BufferedReader(new FileReader(filePath));
            String line = reader.readLine();
            while (line != null) {
                lines.add( line );
                line = reader.readLine();
            }
            return lines;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static int[][] calculateMinimumTransferWay(List<String> lines_v1, List<String> lines_v2 ){
        int size_v1 = lines_v1.size();
        int size_v2 = lines_v2.size();
        int[][] dp = new int[ size_v1 ][ size_v2 ];

        for (int index1 = 0; index1 < size_v1; index1++) {
            String line_v1 = lines_v1.get( index1 );
            for (int index2 = 0; index2 < size_v2; index2++) {
                String line_v2 = lines_v2.get( index2 );
                if( index1 == 0 ){
                    if( index2 == 0 ){
                        if( line_v1.equals( line_v2 ) ){
                            // v1:a
                            // v2:a
                            // 无需任何操作
                            dp[ index1 ][ index2 ] = 0;
                        }else {
                            // 不相同,需要 1 步删除操作,1步插入操作
                            // v1:a
                            // v2:b
                            dp[ index1 ][ index2 ] = 2;
                        }
                    }else {
                        if( getFirstEqualIndex( lines_v2,line_v1,0,index2 ) != -1 ){
                            // v1:      a
                            // v2:...a...
                            // 需要 index2 步插入操作
                            dp[ index1 ][ index2 ] = index2;
                        }else {
                            // v1:      a
                            // v2:...b...
                            // 需要1步删除操作,index2 + 1步插入操作
                            dp[ index1 ][ index2 ] = index2 + 2;
                        }
                    }
                }else {
                    if( index2 == 0 ){
                        if( getFirstEqualIndex(lines_v1, line_v2, 0, index1) != -1 ){
                            // v1:...a...
                            // v2:      a
                            // 需要 index1 步删除操作
                            dp[ index1 ][ index2 ] = index1;
                        }else {
                            // v1:...a...
                            // v2:      b
                            // 需要 index1 + 1 步删除操作,1步插入操作
                            dp[ index1 ][ index2 ] = index1 + 2;
                        }
                    }else {
                        if( line_v1.equals( line_v2 ) ){
                            // v1:...a
                            // v2:...a
                            dp[ index1 ][ index2 ] = dp[index1 - 1][index2 - 1];
                        }else {
                            // v1:...a
                            // v2:...b
                            // sed means "shorest edit distance"
                            int sed1 = dp[index1 - 1][index2];
                            int sed2 = dp[index1 ][index2 - 1];
                            int sed3 = dp[index1 - 1][index2 - 1];
                            int sed = Math.min( Math.min( sed1,sed2 ),sed3 );
                            if( sed1 == sed || sed2 == sed ){
                                dp[ index1 ][ index2 ] = sed + 1;
                            }else {
                                dp[ index1 ][ index2 ] = sed + 2;
                            }
                        }
                    }
                }
            }
        }
        return dp;
    }

    private static int getFirstEqualIndex(List<String> lines, String targetLine, int beginIndex, int endIndex) {
        for (int i = beginIndex; i <=endIndex ; i++) {
            if( targetLine.equals( lines.get( i ) ) ){
                return i;
            }
        }
        return -1;
    }
}

旧版本文本文件 DemoClass1.java:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.similarity.LevenshteinDistance;

@Slf4j
public class DemoClass1 {

    private static final LevenshteinDistance LEVENSHTEIN_DISTANCE = LevenshteinDistance.getDefaultInstance();

    public static String null2emptyWithTrim( String str ){
        if( str == null ){
            str = "";
        }
        str = str.trim();
        return str;
    }

    public static String requiredStringParamCheck(String param, String paramRemark) {
        param = null2emptyWithTrim( param );
        if( param.length() == 0 ){
            String msg = "操作失败,请求参数 \"" + paramRemark + "\" 为空";
            log.error( msg );
            throw new BusinessLogicException( msg );
        }
        return param;
    }

    public static double calculateSimilarity( String str1,String str2 ){
        int distance = LEVENSHTEIN_DISTANCE.apply(str1, str2);
        double similarity = 1 - (double) distance / Math.max(str1.length(), str2.length());
        System.out.println("相似度:" + similarity);
        return similarity;
    }
}

新版本文本文件 DemoClass2.java:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.similarity.LevenshteinDistance;

@Slf4j
public class DemoClass2 {

    private static final LevenshteinDistance LEVENSHTEIN_DISTANCE = LevenshteinDistance.getDefaultInstance();
    private static final LevenshteinDistance LEVENSHTEIN_DISTANCE1 = LevenshteinDistance.getDefaultInstance();
    private static final LevenshteinDistance LEVENSHTEIN_DISTANCE2 = LevenshteinDistance.getDefaultInstance();

    public static String null2emptyWithTrim( String str ){
        // if( str == null ){
        //     str = "";
        // }
        // str = str.trim();
        return str;
    }

    public static String requiredStringParamCheck(String param, String paramRemark) {
        return null;
    }

    public static double calculateSimilarity( String str1,String str2 ){
        try {
            int distance = LEVENSHTEIN_DISTANCE.apply(str1, str2);
            double similarity = 1 - (double) distance / Math.max(str1.length(), str2.length());
            return similarity;
        }catch ( Exception e ){
            e.printStackTrace();
            return 0d;
        }
    }
}

测试输出:


  import com.goldwind.ipark.common.exception.BusinessLogicException;
  import lombok.extern.slf4j.Slf4j;
  import org.apache.commons.text.similarity.LevenshteinDistance;
  
  @Slf4j
- public class DemoClass1 {
+ public class DemoClass2 {
  
      private static final LevenshteinDistance LEVENSHTEIN_DISTANCE = LevenshteinDistance.getDefaultInstance();
+     private static final LevenshteinDistance LEVENSHTEIN_DISTANCE1 = LevenshteinDistance.getDefaultInstance();
+     private static final LevenshteinDistance LEVENSHTEIN_DISTANCE2 = LevenshteinDistance.getDefaultInstance();
  
      public static String null2emptyWithTrim( String str ){
-         if( str == null ){
-             str = "";
-         }
-         str = str.trim();
+         // if( str == null ){
+         //     str = "";
+         // }
+         // str = str.trim();
          return str;
      }
  
      public static String requiredStringParamCheck(String param, String paramRemark) {
-         param = null2emptyWithTrim( param );
-         if( param.length() == 0 ){
-             String msg = "操作失败,请求参数 \"" + paramRemark + "\" 为空";
-             log.error( msg );
-             throw new BusinessLogicException( msg );
-         }
-         return param;
+         return null;
      }
  
      public static double calculateSimilarity( String str1,String str2 ){
-         int distance = LEVENSHTEIN_DISTANCE.apply(str1, str2);
-         double similarity = 1 - (double) distance / Math.max(str1.length(), str2.length());
-         System.out.println("相似度:" + similarity);
-         return similarity;
+         try {
+             int distance = LEVENSHTEIN_DISTANCE.apply(str1, str2);
+             double similarity = 1 - (double) distance / Math.max(str1.length(), str2.length());
+             return similarity;
+         }catch ( Exception e ){
+             e.printStackTrace();
+             return 0d;
+         }
      }
  }

求做小编辑距离的时候,我这里不允许编辑操作,通常的求最小编辑距离一共允许三种操作( 删除、新增、编辑 ),其中一个编辑操作和删除、新增操作的权重都算作一步,我这里不允许编辑操作,比如迫不得已必须用编辑操作时,例如将a 行变为b行,我们必须先删除,后增加,其实等效于允许编辑操作,但是编辑操作权重大一些,为什么这样规定呢?

如上所示,新版本相对于旧版本来说是连续的修改了多行,我们人眼比较习惯这种差异比较后的输出:

而不是这种输出( 虽然逻辑上也对 ):

如果允许编辑操作( 或者编辑操作的权重和删除、新增操作一样时 ),就可能会出现这种情况,整块整块的修改给当做每一行来一个编辑操作。如果不允许编辑操作( 其实等价于将编辑操作的权重设置为删除或者插入操作的2倍 ),那么当出现整块的修改时,差异化比较时,被当做多个单行的修改的概率就被降低了,因为那样的话编辑次数会更多,所以会优先的视为多行的删除( 块删除 )操作和多行的新增( 块新增 )。


http://www.kler.cn/a/159528.html

相关文章:

  • 服务器上安装Orcale数据库以及PL SQL工具(中文)
  • 使用阿里云快速搭建 DataLight 平台
  • openwebui二改界面环境搭建
  • Javascript高级—常见算法
  • 试编写算法将单链表就地逆置(默认是带头节 点,如果是不带头节点地逆置呢?)
  • 在esxi8.0中安装黑群晖的过程记录及小问题处理
  • 2-1、地址加法器CS:IP
  • 【开发实践】使用jstree实现文件结构目录树
  • Go 语言中的反射机制
  • Hadoop学习笔记(HDP)-Part.04 基础环境配置
  • 数字化转型如何落地?_光点科技
  • 在Ubuntu18.04运行qt程序不小心修改内部文件出现The following files have no write permissions的解决方案
  • ARM预取侧信道(Prefetcher Side Channels)攻击与防御
  • 【热点】Docker镜像自动升级?Watchtower帮你搞定!
  • fastapi.templating与HTMLResponse
  • 华为OD机试 - 九宫格按键输入 - 逻辑分析(Java 2023 B卷 200分)
  • 腾讯面试真题(C语言)
  • 【链表Linked List】力扣-83 删除排序链表中的重复元素
  • 有什么可视化数据管理工具?
  • 同旺科技 USB TO RS-485 定制款适配器--- 拆解(一)
  • MyBatisPlus+SpringBoot+JavaFX连接查询
  • 技术阅读周刊第第8️⃣期
  • Temporal table join requires an equality condition on fields of table
  • 【2】基于多设计模式下的同步异步日志系统-设计模式
  • git小白初学习
  • 腾讯云位居中国分布式关系型数据库“领导者”类别