142

突出PHP中两个字符串之间差异的最简单方法是什么?

我正在按照 Stack Overflow 编辑历史页面的思路进行思考,其中新文本为绿色,删除的文本为红色。如果有任何预先编写的函数或类可用,那将是理想的。

4

15 回答 15

80

刚刚写了一个类来计算最小(不是字面意思)的编辑次数,以将一个字符串转换为另一个字符串:

http://www.raymondhill.net/finediff/

它有一个静态函数来呈现一个 HTML 版本的差异。

这是第一个版本,可能会得到改进,但它现在工作得很好,所以我把它扔在那里,以防有人需要像我需要的那样有效地生成一个紧凑的差异。

编辑:现在在 Github 上: https ://github.com/gorhill/PHP-FineDiff

于 2011-02-22T19:23:45.283 回答
45

您可以使用 PHP Horde_Text_Diff 包。

但是,此软件包不再可用。

于 2008-11-26T16:32:53.230 回答
26

这是一个不错的,也是 http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

解决问题并不像看起来那么简单,在我弄清楚之前,这个问题困扰了我大约一年。我设法用 PHP 用 18 行代码编写了我的算法。这不是进行差异的最有效方法,但它可能是最容易理解的。

它的工作原理是查找两个字符串共有的最长单词序列,并递归查找字符串其余部分的最长序列,直到子字符串没有共同的单词。此时它将剩余的新词添加为插入,剩余的旧词作为删除。

你可以在这里下载源代码:PHP SimpleDiff ...

于 2010-09-24T08:15:41.927 回答
24

如果你想要一个健壮的库,Text_Diff(一个 PEAR 包)看起来很不错。它有一些非常酷的功能。

于 2008-11-26T16:32:34.043 回答
23

这是一个简短的函数,可用于区分两个数组。它实现了LCS算法:

function computeDiff($from, $to)
{
    $diffValues = array();
    $diffMask = array();

    $dm = array();
    $n1 = count($from);
    $n2 = count($to);

    for ($j = -1; $j < $n2; $j++) $dm[-1][$j] = 0;
    for ($i = -1; $i < $n1; $i++) $dm[$i][-1] = 0;
    for ($i = 0; $i < $n1; $i++)
    {
        for ($j = 0; $j < $n2; $j++)
        {
            if ($from[$i] == $to[$j])
            {
                $ad = $dm[$i - 1][$j - 1];
                $dm[$i][$j] = $ad + 1;
            }
            else
            {
                $a1 = $dm[$i - 1][$j];
                $a2 = $dm[$i][$j - 1];
                $dm[$i][$j] = max($a1, $a2);
            }
        }
    }

    $i = $n1 - 1;
    $j = $n2 - 1;
    while (($i > -1) || ($j > -1))
    {
        if ($j > -1)
        {
            if ($dm[$i][$j - 1] == $dm[$i][$j])
            {
                $diffValues[] = $to[$j];
                $diffMask[] = 1;
                $j--;  
                continue;              
            }
        }
        if ($i > -1)
        {
            if ($dm[$i - 1][$j] == $dm[$i][$j])
            {
                $diffValues[] = $from[$i];
                $diffMask[] = -1;
                $i--;
                continue;              
            }
        }
        {
            $diffValues[] = $from[$i];
            $diffMask[] = 0;
            $i--;
            $j--;
        }
    }    

    $diffValues = array_reverse($diffValues);
    $diffMask = array_reverse($diffMask);

    return array('values' => $diffValues, 'mask' => $diffMask);
}

它生成两个数组:

  • values 数组:出现在差异中的元素列表。
  • 掩码数组:包含数字。0:不变,-1:删除,1:添加。

如果您用字符填充数组,它可用于计算内联差异。现在只需一步即可突出差异:

function diffline($line1, $line2)
{
    $diff = computeDiff(str_split($line1), str_split($line2));
    $diffval = $diff['values'];
    $diffmask = $diff['mask'];

    $n = count($diffval);
    $pmc = 0;
    $result = '';
    for ($i = 0; $i < $n; $i++)
    {
        $mc = $diffmask[$i];
        if ($mc != $pmc)
        {
            switch ($pmc)
            {
                case -1: $result .= '</del>'; break;
                case 1: $result .= '</ins>'; break;
            }
            switch ($mc)
            {
                case -1: $result .= '<del>'; break;
                case 1: $result .= '<ins>'; break;
            }
        }
        $result .= $diffval[$i];

        $pmc = $mc;
    }
    switch ($pmc)
    {
        case -1: $result .= '</del>'; break;
        case 1: $result .= '</ins>'; break;
    }

    return $result;
}

例如。:

echo diffline('StackOverflow', 'ServerFault')

将输出:

S<del>tackO</del><ins>er</ins>ver<del>f</del><ins>Fau</ins>l<del>ow</del><ins>t</ins> 

小号粘性服务器F错误

补充笔记:

  • diff 矩阵需要 (m+1)*(n+1) 个元素。因此,如果您尝试区分长序列,您可能会遇到内存不足错误。在这种情况下,首先区分较大的块(例如行),然后在第二遍中区分它们的内容。
  • 如果从开头和结尾修剪匹配元素,然后仅在不同的中间运行算法,则可以改进算法。后一个(更臃肿的)版本也包含这些修改。
于 2014-02-25T17:07:42.000 回答
6

xdiff 还有一个 PECL 扩展:

尤其:

PHP手册中的示例:

<?php
$old_article = file_get_contents('./old_article.txt');
$new_article = $_POST['article'];

$diff = xdiff_string_diff($old_article, $new_article, 1);
if (is_string($diff)) {
    echo "Differences between two articles:\n";
    echo $diff;
}
于 2012-05-01T11:42:35.327 回答
5

我在显示的基于 PEAR 的替代方案和更简单的替代方案上都遇到了可怕的麻烦。因此,这是一个利用 Unix diff 命令的解决方案(显然,您必须在 Unix 系统上或有一个有效的 Windows diff 命令才能工作)。选择您最喜欢的临时目录,并根据需要更改异常以返回代码。

/**
 * @brief Find the difference between two strings, lines assumed to be separated by "\n|
 * @param $new string The new string
 * @param $old string The old string
 * @return string Human-readable output as produced by the Unix diff command,
 * or "No changes" if the strings are the same.
 * @throws Exception
 */
public static function diff($new, $old) {
  $tempdir = '/var/somewhere/tmp'; // Your favourite temporary directory
  $oldfile = tempnam($tempdir,'OLD');
  $newfile = tempnam($tempdir,'NEW');
  if (!@file_put_contents($oldfile,$old)) {
    throw new Exception('diff failed to write temporary file: ' . 
         print_r(error_get_last(),true));
  }
  if (!@file_put_contents($newfile,$new)) {
    throw new Exception('diff failed to write temporary file: ' . 
         print_r(error_get_last(),true));
  }
  $answer = array();
  $cmd = "diff $newfile $oldfile";
  exec($cmd, $answer, $retcode);
  unlink($newfile);
  unlink($oldfile);
  if ($retcode != 1) {
    throw new Exception('diff failed with return code ' . $retcode);
  }
  if (empty($answer)) {
    return 'No changes';
  } else {
    return implode("\n", $answer);
  }
}
于 2011-11-30T16:22:43.420 回答
5

这是我找到的最好的一个。

http://code.stephenmorley.org/php/diff-implementation/

在此处输入图像描述

于 2015-04-08T14:29:43.903 回答
2

您正在寻找的是“差异算法”。一个快速的谷歌搜索让我找到了这个解决方案。我没有测试它,但也许它会做你需要的。

于 2008-11-26T16:28:25.097 回答
2

Neil Frasers diff_match_patch 的 php 端口(Apache 2.0 许可)

于 2012-04-25T22:28:05.727 回答
2

我建议从 PHP 核心查看这些很棒的函数:

similar_text — 计算两个字符串之间的相似度

http://www.php.net/manual/en/function.similar-text.php

levenshtein — 计算两个字符串之间的 Levenshtein 距离

http://www.php.net/manual/en/function.levenshtein.php

soundex — 计算字符串的 soundex 键

http://www.php.net/manual/en/function.soundex.php

metaphone — 计算字符串的变音键

http://www.php.net/manual/en/function.metaphone.php

于 2014-04-24T05:18:01.367 回答
1

我尝试了一种简单的方法,其中包含两个文本框和一些颜色样式。注意:我的差异检查器只会突出单词而不是字符的差异。

    <?php
    $valueOne = $_POST['value'] ?? "";
    $valueTwo = $_POST['valueb'] ?? "" ;
    
    $trimValueOne = trim($valueOne);
    $trimValueTwo = trim($valueTwo);

    $arrayValueOne = explode(" ",$trimValueOne);
    $arrayValueTwo = explode(" ",$trimValueTwo);

    $allDiff = array_merge(array_diff($arrayValueOne, $arrayValueTwo), array_diff($arrayValueTwo, $arrayValueOne));
    if(array_intersect($arrayValueOne,$allDiff) && array_intersect($arrayValueTwo,$allDiff)){

        if(array_intersect($arrayValueOne,$allDiff)){
            $highlightArr = array_intersect($arrayValueOne,$allDiff);
            $highlightArrValue = array_values($highlightArr);
            for ($i=0; $i <count($arrayValueOne) ;$i++) { 
                for ($j=0; $j <count($highlightArrValue) ; $j++) { 
                    if($arrayValueOne[$i] == $highlightArrValue[$j]){
                        $arrayValueOne[$i] = "<span>".$arrayValueOne[$i]."</span>";
                    }
                }
            }
            $strOne = implode(" ",$arrayValueOne);
            echo "<p class = \"one\">{$strOne}</p>";
        }if(array_intersect($arrayValueTwo,$allDiff)){
        $highlightArr = array_intersect($arrayValueTwo,$allDiff);
        $highlightArrValue = array_values($highlightArr);
        for ($i=0; $i <count($arrayValueTwo) ;$i++) { 
            for ($j=0; $j <count($highlightArrValue) ; $j++) { 
                    if($arrayValueTwo[$i] == $highlightArrValue[$j]){
                        $arrayValueTwo[$i] = "<span>".$arrayValueTwo[$i]."</span>";
                    }
                }
        }
        $strTwo = implode(" ",$arrayValueTwo);
        echo "<p class = \"two\">{$strTwo}</p>";
        }
    }elseif(!(array_intersect($arrayValueOne,$allDiff) && array_intersect($arrayValueTwo,$allDiff))){
        if($trimValueOne == $trimValueTwo){
            echo"<p class = \"one green\">$trimValueOne</p></p>";
            echo"<p class = \"two green\">$trimValueTwo</p></p>";
        }
        else{
            echo"<p class = \"one \">$trimValueOne</p></p>";
            echo"<p class = \"two \">$trimValueTwo</p></p>";
        }

    }
?>


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <form method="post" action="">
    <textarea type="text" name="value" placeholder="enter first text"></textarea>
    <textarea type="text" name="valueb" placeholder="enter second text"></textarea>
    <input type="submit">
    </form>
</body>
</html>
于 2021-08-27T11:42:45.013 回答
0

我遇到了 Chris Boulton 基于 Python difflib 编写的 PHP diff 类,这可能是一个很好的解决方案:

PHP 差异库

于 2015-03-11T20:51:40.117 回答
0

另一种解决方案(用于并排比较而不是统一视图):https ://github.com/danmysak/side-by-side 。

于 2020-01-15T15:30:53.267 回答
0

对于那些只是寻找一个非常简单的函数来在字符串 A 中而不是在字符串 B 中查找字符的人,我编写了这个快速且非常简单的函数。

function strdiff($a,$b){

    $a = str_split($a);
    $b = str_split($b);

    return array_diff($a,$b);

}
于 2022-01-17T11:13:22.670 回答