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

news2025/1/17 8:48:54
测试类 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.coloradmin.cn/o/1280838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java操作windows系统功能案例(三)

以下是一些 Java 操作 Windows 系统功能的案例&#xff1a; 打开 Windows 计算器 public class Calculator {public static void main(String[] args) throws Exception {Runtime.getRuntime().exec("calc.exe");} }打开 Windows 默认浏览器 public class Browser…

数学字体 Mathematical fonts

Mathematical fonts 数学字体&#xff1a; ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzRQSZ \\ \mathcal{ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzRQSZ} \\ \mathfrak{ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzRQSZ} \\ \mathbb{ABC…

332. 重新安排行程

题目描述 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&#xff0c;所以该行程必须从 JFK 开始。如…

谷歌 Gemini 模型发布计划推迟:无法可靠处理部分非英语沟通

本心、输入输出、结果 文章目录 谷歌 Gemini 模型发布计划推迟&#xff1a;无法可靠处理部分非英语沟通前言由谷歌 CEO 桑达尔・皮查伊做出决策从一开始&#xff0c;Gemini 的目标就是多模态、高效集成工具、API花有重开日&#xff0c;人无再少年实践是检验真理的唯一标准 谷歌…

《微信小程序开发从入门到实战》学习四十

4.2 云开发JSON数据库 4.2.11 更新数据 使用数据库API更新数据有两种方法&#xff1a;一.将记录局部更新的update方法&#xff1b;二.以替换的方式更新记录的set方法 update方法可以局部更新一个记录或一个集合的多个记录&#xff0c;更新时只有指定字段更新&#xff0c;其他…

【数电笔记】18-卡诺图化简

目录 说明&#xff1a; 用卡诺图化简逻辑函数 1. 公式法化简与卡诺图化简对比 2. 化简依据 3. 化简规律 3.1 两个小方块相邻 3.2 四个小方块相邻 3.3 八个小方块相邻 4. 卡诺图化简法步骤 4.1 例1 4.2 例2 5. 画卡诺圈规则 5.1 例1 6. 特殊情况 6.1 例1 6.2 例…

算法设计与实现--贪心篇

贪心算法 贪心算法是一种在每一步选择中都采取当前状态下最优决策的算法&#xff0c;以期望能够通过一系列局部最优的选择达到全局最优。贪心算法的关键是定义好局部最优的选择&#xff0c;并且不回退&#xff0c;即一旦做出了选择&#xff0c;就不能撤销。 一般来说&#xf…

【栈和队列(2)】

文章目录 前言队列队列方法队列模拟实现循环队列练习1 队列实现栈 前言 队列和栈是相反的&#xff0c;栈是先进后出&#xff0c;队列是先进先出&#xff0c;相当于排队打饭&#xff0c;排第一的是最先打到饭出去的。 队列 队列&#xff1a;只允许在一端进行插入数据操作&…

C++基础从0到1入门编程(六)- 类继承、类多态

系统学习C&#xff0c;本章将记录类继承、类多态的相关概念 方便自己日后复习&#xff0c;错误的地方希望积极指正 往期文章&#xff1a; C基础从0到1入门编程&#xff08;一&#xff09; C基础从0到1入门编程&#xff08;二&#xff09; C基础从0到1入门编程&#xff08;三&am…

西瓜书-主要符号表

主要符号表 LaTeX符号说明How to read letter?\mathit{x}标量\boldsymbol{x}向量\mathrm{x}变量集\mathbf{A}矩阵\mathbf{I}单位阵\mathcal{X}样本空间或状态空间calligraphic X\mathcal{D}概率分布Ɗ calligraphic D\mathit{H}数据样本&#xff08;数据集)\mathcal{H}假设空…

智能优化算法应用:基于差分进化算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于差分进化算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于差分进化算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.差分进化算法4.实验参数设定5.算法结果6.参考…

IM通信技术快速入门:短轮询、长轮询、SSE、WebSocket

文章目录 前言即时通讯常用技术 短轮询&#xff08;Short Polling&#xff09;实现原理优点缺点 长轮询(Long Polling)实现原理改进点基于iframe的长轮询实现原理总结 Server-Sent Events&#xff08;SSE&#xff09;实现原理浏览器对 SSE 的支持情况SSE vs WebSocket总结 WebS…

基于英特尔平台及OpenVINO2023工具套件优化文生图任务

当今&#xff0c;文生图技术在很多领域都得到了广泛的应用。这种技术可以将文本直接转换为逼真的图像&#xff0c;具有很高的实用性和应用前景。然而&#xff0c;由于文生成图任务通常需要大量的计算资源和时间&#xff0c;如何在英特尔平台上高效地完成这些计算是一个重要的挑…

基于Java SSM框架+Vue实现企业公寓后勤管理系统项目【项目源码+论文说明】

基于java的SSM框架Vue实现企业宿舍后勤管理网站演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所…

RC低通滤波电路直接带载后会发生什么?

1、滤波的含义 滤波是频域范畴&#xff0c;它说的是不同频率的信号经过一个电路处理后&#xff0c;信号发生变化的问题&#xff0c;变化包含了原始信号幅值和相位的变化&#xff0c;滤波电路对信号的幅值做出的响应称为幅频响应&#xff0c;对信号相位做出的反应称为相频响应。…

【MySQL】视图 + 用户管理

视图 前言正式开始视图用户管理user表创建新用户修改用户密码权限管理给用户赋权剥夺权限 前言 本篇所讲的视图和我上一篇事务中所讲的读视图不是一个东西&#xff0c;二者没有任何关系&#xff0c;如果看过我前一篇博客的同学不要搞混了。 其实视图和用户管理本来是想着分开…

perl脚本批量处理代码中的中文注释乱码的问题

代码中统一使用utf-8编码是最好的&#xff0c;但是有一些多人合作的项目或者一些历史遗留代码&#xff0c;常见一些中文注释乱码的问题。这里以一个开源项目evpp为例子 evpp。以项目中的一个commit id为例&#xff1a; 477033f938fd47dfecde43c82257cd286d9fa38e &#xff0c; …

数据结构之堆排序以及Top-k问题详细解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 数据结构初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力 目录 1.前言 2.堆排序 2.1降序排序 2.2时间复杂…

充电桩新老国标兼容性分析

1、背景介绍 1.1、充电桩相关标准发展历程 1.2、兼容性分析历史 1.3、兼容性分析的目的 1.4、兼容性分析的内容 2、B类协议兼容性分析 2.1、协议分层结构 2.2、链路层分析 2.3、版本协商与链路检测 ## 2.4、传输层分析 2.5、应用层 2.5.1、应用层数据 2.5.2、应用层数据…

谈谈MYSQL索引

基本介绍 索引是帮助MySQL高效获取数据的数据结构&#xff0c;主要是用来提高数据检索的效率&#xff0c;降低数据库的IO成本&#xff0c;同时通过索引列对数据进行排序&#xff0c;降低数据排序的成本&#xff0c;也能降低了CPU的消耗。 通俗来说, 索引就相当于一本书的目录,…