เน้นความแตกต่างระหว่างสองสายใน 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
น่าเสียดายที่มันต้องมีลูกแพร์ ลูกแพร์พึ่งพาอาศัยดูด
Rudie

7
จากเว็บไซต์ใหม่: "อัปเดต: อินไลน์เรนเดอร์ตอนนี้เป็นส่วนหนึ่งของแพ็คเกจ Text_Diff PEAR คุณไม่จำเป็นต้องใช้แฮ็คที่แสดงที่นี่อีกต่อไป" ดังนั้นเพียงใช้ Text_Diff ตอนนี้
Mat

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

! ที่น่าตื่นตาตื่นใจ ฉันหมายถึงตัวอย่างออนไลน์พร้อมรหัสตัวอย่าง ความแตกต่างในระดับถ่านที่สมบูรณ์แบบ เพียงแค่ว้าว! : O ขอบคุณ!
Filip OvertoneSinger Rydlo

2
ดูเหมือนว่าตอนนี้ตัวแยกgithub.com/BillyNate/PHP-FineDiffจะล้ำหน้าที่สุดและรองรับมัลติไบต์ที่มีการเข้ารหัสที่แตกต่างกัน github.com/xrstf/PHP-FineDiffคือ 404ing @ activout.se
Kangur

24

ถ้าคุณต้องการห้องสมุดที่มีประสิทธิภาพText_Diff (แพ็คเกจแพร์) ดูเหมือนจะค่อนข้างดี มันมีคุณสมบัติเจ๋ง ๆ


6
PHP Inline-Diff ดังกล่าวข้างต้น ".. ใช้ Text_Diff จากลูกแพร์เพื่อคำนวณค่าต่าง" :)
MN

ลิงก์เสีย ไม่พบแพ็คเกจ นี่เป็นแพคเกจ Diff แบบเดียวกับที่ใช้กับ Wordpress เวอร์ชันล่าสุด
Basil Musa

24

นี่เป็นสิ่งที่ดีเช่นกัน http://paulbutler.org/archives/a-simple-diff-algorithm-in-php/

การแก้ปัญหานั้นไม่ง่ายอย่างที่คิดและปัญหาก็รบกวนฉันมาประมาณหนึ่งปีก่อนที่ฉันจะรู้ได้ ฉันจัดการเพื่อเขียนอัลกอริทึมของฉันใน PHP ในรหัส 18 บรรทัด มันไม่ใช่วิธีที่มีประสิทธิภาพที่สุดในการทำ diff แต่มันอาจเป็นวิธีที่เข้าใจง่ายที่สุด

มันทำงานโดยการค้นหาลำดับของคำที่ยาวที่สุดที่ใช้ร่วมกันกับทั้งสองสตริงและวนซ้ำในลำดับที่ยาวที่สุดของส่วนที่เหลือของสตริงจนกระทั่งสตริงย่อยไม่มีคำที่เหมือนกัน ณ จุดนี้จะเพิ่มคำศัพท์ใหม่ที่เหลือเป็นการแทรกและคำเก่าที่เหลือเป็นการลบ

คุณสามารถดาวน์โหลดซอร์สได้ที่นี่: 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 เพื่อความง่าย
Parag Tyagi

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

มันสร้างสองอาร์เรย์:

  • values ​​array: รายการองค์ประกอบตามที่ปรากฏในส่วนต่าง
  • หน้ากากอาร์เรย์: มีตัวเลข 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> 

StackOerverฟอลโอ๊ยเสื้อ

หมายเหตุเพิ่มเติม:

  • diff matrix ต้องการองค์ประกอบ (m + 1) * (n + 1) ดังนั้นคุณสามารถเรียกใช้หน่วยความจำผิดพลาดถ้าคุณพยายามที่จะวนเวียนยาว ในกรณีนี้แตกต่างชิ้นที่ใหญ่กว่า (เช่นเส้น) ก่อนจากนั้นจึงกระจายเนื้อหาในรอบที่สอง
  • อัลกอริทึมสามารถปรับปรุงได้ถ้าคุณตัดแต่งองค์ประกอบการจับคู่จากจุดเริ่มต้นและจุดสิ้นสุดจากนั้นเรียกใช้อัลกอริทึมบนกลางที่แตกต่างกันเท่านั้น หลัง (อ้วนมากขึ้น) รุ่นมีการปรับเปลี่ยนเหล่านี้มากเกินไป

นี่คือเรียบง่ายมีประสิทธิภาพและข้ามแพลตฟอร์ม ฉันใช้เทคนิคนี้กับ explode () ในขอบเขตต่าง ๆ (บรรทัดหรือคำ) เพื่อให้ได้ผลลัพธ์ที่แตกต่างกันตามความเหมาะสม ทางออกที่ดีมากขอบคุณ!
ลุงรหัสลิง

มันบอกว่าcomputeDiff is not found
ichimaru

@ichimaru คุณวางทั้งสองฟังก์ชันแล้วหรือยัง
Calmarius

@Calarius ไม่เห็นฟังก์ชั่นอื่น ๆ ... ฉันสาบาน! มันใช้งานได้แล้วตอนนี้ขอบคุณ!
ichimaru

ขอบคุณหนึ่งอันนี้ค่อนข้างสะดวกในการค้นหาคำตอบที่ต่างออกไป
Karan Sharma

6

นอกจากนี้ยังมีส่วนขยาย PECL สำหรับ xdiff:

โดยเฉพาะอย่างยิ่ง:

  • xdiff_string_diff - สร้าง diff แบบรวมของสองสาย

ตัวอย่างจากคู่มือ 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/l ไลบรารี/Horde_Text_Diff/download
Mike Purcell

มีขั้นตอนการติดตั้งอย่างง่ายสำหรับ XDiff ของ PHP หรือไม่ (สำหรับ Debian Linux)
Peter Krauss

@ MikePurcell ตามความเป็นจริงมันยังคงอยู่ เวอร์ชันเสถียรล่าสุด 2.0.1 ที่สนับสนุน PHP 7 ได้รับการเผยแพร่ใน 2016-05-16
2513149

@ PeterKrauss ใช่มี ดูคำถามนี้: serverfault.com/questions/362680/…
2513149

5

ฉันมีปัญหาอย่างมากกับทั้งลูกแพร์และทางเลือกที่ง่ายกว่าที่แสดง ดังนั้นนี่คือโซลูชันที่ใช้ประโยชน์จากคำสั่ง 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
Andy

1
การเชื่อมโยงปัจจุบันคือcode.iamkate.com/php/diff-implementation ฉันทดสอบแล้วและไม่รองรับ UTF-8
Kangur

3

สิ่งที่คุณกำลังมองหาคือ "อัลกอริทึมต่าง" การค้นหา google อย่างรวดเร็วทำให้ฉันเข้าสู่โซลูชันนี้ ฉันไม่ได้ทดสอบ แต่อาจจะทำในสิ่งที่คุณต้องการ


ฉันเพิ่งทดสอบสคริปต์นั้นและใช้งานได้ดีการดำเนินการ diff เสร็จสิ้นอย่างรวดเร็ว (ใช้เวลาประมาณ 10ms ในการประมวลผลย่อหน้าสั้น ๆ ที่ฉันทดสอบ) และสามารถตรวจจับได้เมื่อมีการเพิ่มตัวแบ่งบรรทัด การเรียกใช้โค้ดตาม - คือการสร้างคู่ของการแจ้งเตือน PHP ที่คุณอาจต้องการแก้ไข แต่นอกเหนือจากนั้นมันเป็นทางออกที่ดีมากถ้าคุณต้องการแสดงความแตกต่างแบบอินไลน์แทนที่จะใช้มุมมอง diff-by-side แบบดั้งเดิม
Noel Whitemore


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 - คำนวณคีย์ metaphone ของสตริง

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



0

วิธีการแก้ปัญหาอื่น (เปรียบเทียบด้านข้างตรงข้ามกับมุมมองแบบครบวงจร): https://github.com/danmysak/side-by-side

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.