Виділіть різницю між двома рядками в PHP


136

Який найпростіший спосіб виділити різницю між двома рядками в PHP?

Я думаю, що на сторінці історії редагування стека переповнення новий текст зеленим кольором, а вилучений текст - червоним кольором. Якщо є якісь заздалегідь написані функції або класи, це було б ідеально.

Відповіді:


42

Ви змогли скористатися пакетом PHP Horde_Text_Diff.

Однак цей пакет більше не доступний.


1
посилання більше не працює. це якесь інше рішення зараз у 2011 році? ;-) чи можна отримати вихід на зразок цього tortoisesvn.tigris.org/images/TMerge2Diff.png
Glavić

3
Сайта вже немає, але в archive.org є копія сайту: web.archive.org/web/20080506155528/http://software.zuavra.net/…
R. Hill,

15
Шкода, що це вимагає PEAR. Залежність від PEAR смокче.
Rudie

7
З нового веб-сайту: "Оновлення: вбудований рендер тепер є рідною частиною пакету Text_Diff PEAR. Вам більше не потрібно використовувати хак, представлений тут." Тому просто використовуйте Text_Diff зараз.
Мат

11
GPL не просто безкоштовно використовувати. Це змушує ваш модуль / проект також бути GPL.
Parris

76

Просто написав клас для обчислення найменшої (не сприймається буквально) кількості редагувань, щоб перетворити одну рядок в іншу рядок:

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

Він має статичну функцію для надання HTML-версії розл.

Це перша версія, і, ймовірно, буде вдосконалена, але вона працює чудово, як і зараз, тому я викидаю її на випадок, якщо комусь потрібно створити компактний розбір, як мені потрібно.

Редагувати: Зараз на Github: https://github.com/gorhill/PHP-FineDiff


3
Я спробую вилку на github.com/xrstf/PHP-FineDiff, щоб отримати багатобайтову підтримку!
Activout.se

1
@R. Пагорб - прекрасно працює і для мене. Це дійсно краща відповідь, ніж поточна, яка, здається, не існує.
Wonko the Sane

Будь які оновлення? У ньому сказано, що не вдалося включити файл "Texts / Diff.php", і він не знаходиться в zip.
SISYN

Дивовижний! Я маю на увазі інтернет-демонстрацію з прикладом коду. Ідеальні відмінності в рівні чар. Просто WoW! : O Дякую!
Filip OvertoneSinger Rydlo

2
Схоже, зараз вилка github.com/BillyNate/PHP-FineDiff є найбільш впевненою, і вона підтримує багатобайти з різними кодуванням. github.com/xrstf/PHP-FineDiff є 404ing @ activout.se
Kangur

24

Якщо ви хочете надійну бібліотеку, Text_Diff (пакет PEAR) виглядає досить непогано. Він має кілька цікавих особливостей.


6
PHP Inline-Diff, згаданий вище, ".. використовує Text_Diff з PEAR для обчислення різниці". :)
MN

Посилання розірвано. Не можу знайти пакунок. Це той самий пакет Diff, який використовується в останній версії Wordpress.
Василь Муса

24

Це добре, також http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

Вирішити проблему не так просто, як здається, і проблема турбувала мене близько року, перш ніж я зрозумів це. Мені вдалося написати свій алгоритм у PHP, у 18 рядках коду. Це не найефективніший спосіб зробити різницю, але це, мабуть, найпростіший для розуміння.

Він працює, знаходячи найдовшу послідовність слів, спільну для обох рядків, і рекурсивно знаходить найдовші послідовності залишків рядка, поки підрядки не мають спільних слів. У цей момент він додає решту нових слів як вставку, а решту старих слів як видалення.

Ви можете завантажити джерело тут: PHP SimpleDiff ...


1
Я також вважаю це дуже корисним! Не такий складний, як груші.
dgavey

Це дає мені помилку тут:if($matrix[$oindex][$nindex] > $maxlen){ Undefined variable: maxlen
динамічний

Гаразд, ви опублікували порядок вирішення цього питання. :) чому ви не редагуєте його в початковому коді? Все одно дякую +1 ... хм добре, що ти не автор
динамічний

1
ось остання версія 2010 року: github.com/paulgb/simplediff/blob/master/simplediff.php
rsk82

Власне, +1 для простоти
Параг Тяги

17

Ось коротка функція, яку ви можете використовувати для розмежування двох масивів. Він реалізує алгоритм 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);
}

Він генерує два масиви:

  • масив значень: список елементів, як вони відображаються в розл.
  • масив масок: містить числа. 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> 

StackOerverfFaulсовт

Додаткові нотатки:

  • Матриця diff вимагає (m + 1) * (n + 1) елементів. Таким чином, ви можете зіткнутися з помилками пам'яті, якщо спробувати розрізняти довгі послідовності. У цьому випадку спочатку різняться великі шматки (наприклад, рядки), а потім відрізняються їх вмістом у другому проході.
  • Алгоритм можна вдосконалити, якщо обрізати відповідні елементи з початку і в кінці, а потім запустити алгоритм лише на іншій середині. Остання (більш роздута) версія містить ці зміни теж.

це проста, ефективна та крос-платформа; Я використовував цю техніку з explode () на різних кордонах (рядку чи слові), щоб отримати різні результати, де це доречно. Дуже приємне рішення, дякую!
Дядько Код Мавпи

там сказаноcomputeDiff is not found
ichimaru

@ichimaru Ви вставили обидві функції?
Кальмарій

@Calmarius не бачив іншої функції ... клянусь! його працює зараз дякую!
ічімару

Дякую, це досить зручно, щоб дізнатись відмінності, ніж прийнята відповідь.
Каран Шарма

6

Існує також розширення PECL для xdiff:

Зокрема:

Приклад з 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;
}

1
Розширення xdiff pecl більше не підтримується, мабуть, стабільний випуск не робився з 2008-07-01, згідно з pecl.php.net/package/xdiff , я закінчила свою пропозицію прийнятою відповіддю, оскільки це набагато новіше , horde.org/libraries/Horde_Text_Diff/download
Майк Персел

Існує проста процедура встановлення для XDiff PHP? (для Debian Linux)
Пітер Краусс

@MikePurcell, по суті, він все ще підтримується. Остання стабільна версія 2.0.1, що підтримує PHP 7, була випущена 2016-05-16.
користувач2513149

@PeterKrauss, так, є. Погляньте на це питання: serverfault.com/questions/362680/…
користувач2513149

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);
  }
}

4

Це найкраще, що я знайшов.

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

введіть тут опис зображення


3
Не працює належним чином з UTF-8. Він використовує доступ до масиву на рядках, який розглядає кожен символ як один байт. Має бути легко фіксується жорстким за допомогою mb_split.
Gellweiler

1
Ось швидке виправлення. Просто замініть $sequence1 = $string1; $sequence2 = $string2; $end1 = strlen($string1) - 1; $end2 = strlen($string2) - 1;на$sequence1 = preg_split('//u', $string1, -1, PREG_SPLIT_NO_EMPTY); $sequence2 = preg_split('//u', $string2, -1, PREG_SPLIT_NO_EMPTY); $end1 = count($sequence1) - 1; $end2 = count($sequence2) - 1;
Gellweiler

У цього класу не вистачає пам'яті, використовуючи символьний режим у функції computeTable.
Енді

1
Поточне посилання - code.iamkate.com/php/diff-implementation . Я перевірив це, і він не підтримує UTF-8.
Кангур

3

Що ви шукаєте, це "алгоритм різниці". Швидкий пошук Google привів мене до цього рішення . Я цього не перевіряв, але, можливо, він зробить те, що потрібно.


Я щойно перевірив цей сценарій, і він працює добре - дію diff закінчується дуже швидко (потрібно близько 10 мс для обробки короткого абзацу, який я перевірив), і він зміг визначити, коли було додано розрив рядка. Запуск коду як є генерує декілька повідомлень PHP, які ви можете виправити, але крім цього, це дуже хороше рішення, якщо вам потрібно показати різницеві відмінності, а не використовувати традиційний побічний розгляд розгляду.
Ноель Вітмор


2

Я рекомендую переглянути ці дивовижні функції з ядра PHP:

similar_text - Обчисліть подібність двох рядків

http://www.php.net/manual/en/function.s подобни-text.php

Левенштайн - Обчисліть відстань Левенштейна між двома струнами

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

soundex - Обчисліть ключ soundex рядка

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

метафон - обчислити метафоновий ключ рядка

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



Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.