logo


home map contact


Если вы видите это сообщение - значит вы используете браузер Internet Explorer. Негативное отношение к этому браузеру сложилось не только у владельца данного ресурса, но и у подавляющего большинства людей, разбирающихся в web технологиях. Попробуйте установить один из браузеров по ссылке ниже, возможно вам он тоже понравится больше!

Opera, the fastest and most secure web browser     Spread Firefox Affiliate Button

выделение различий в текстах (svn, diff)

Данная статья рассказывает о том как выделить различия в двух текстах, вроде того, как это делает svn. Допустим, у нас есть текст (начальный текст): aa bb cc dd ee ff
и его модифицированная копия (Измененный текст): aa bb xx cc dx ee
и нам нужно выделить различия. Алгоритм решения этой задачи следующий: 1) каждой строке присвоим уникальное имя (индекс), чтобы проше было работать составим таблицу:

aa 1
bb 2
cc 3
dd 4
ee 5
ff 6
xx 7
dx 8


2) теперь переведем строки согласно этой таблице:
aa bb cc dd ee ff => 1 2 3 4 5 6
aa bb xx cc dx ee => 1 2 7 3 8 5

3) далее используем специальный алгоритм - Алгоритм выбора наибольшей общей последовательности (longest common subsequence, LCS). Пока не будем разбираться как он работает. Среди наших строк:

Строка 1 1 2 3 4 5 6
Строка 2 1 2 7 3 8 5
Результат: 1 2 3 5


1 2 3 5 - самая длинная общая часть (эта часть одинакова с учетом, что где-то что-то вставили, где-то удалили)

4) далее поочередно просматриваем обе строки, и если, скажем, в одной строке символ появился, а в другой нет, значит он был вставлен и т.д.

1 строка2 строкаобщая частьрез-тат сравнения
1 1 1 общий
2 2 2 общий
3 7 3 7 помечаем как вставленную и "сдвигаем" 2-ю строку вверх
3 3 3 общий
4 8 5 не общий, так как нет в общей строке - значит помечаем как измененную, т.к. в общей строке не совпало то в ней не переходим на след. символ, а в остальных переходим
5 5 5 общий
6 т.к. есть в 1-ой, а во второй нет - удаленная


5) теперь выделив все что было нужно просто заменяем индентификаторы реальными строчками из уникальной таблицы, которую получили в 1-ом пункте и получаем конечный результат.

Код программы выполняющей эти действия:
function GetLCSAlgoritm(&$_a, &$_b) {
	$a = explode(" ",$_a);
	$b = explode(" ",$_b);
	$maxLen = array();		
	for($i=0, $x=count($a); $i<=$x; $i++) {
		$maxLen[$i] = array();
		for($j=0, $y=count($b); $j<=$y; $j++) $maxLen[$i][$j] = '';
	}
	for($i=count($a)-1; $i>=0; $i--) {		
		for($j=count($b)-1; $j>=0; $j--) {
			if($a[$i] == $b[$j]) $maxLen[$i][$j] = 1+$maxLen[$i+1][$j+1];
			else $maxLen[$i][$j] = max($maxLen[$i+1][$j],$maxLen[$i][$j+1]);
		}
	}
	
	$rez = "";
	for($i=0, $j=0; $maxLen[$i][$j]!=0 && $i<$x && $j<$y;) {
		if($a[$i] == $b[$j]) {
			$rez .= $a[$i]." ";
			$i++;
			$j++;
		}
		else {
			if($maxLen[$i][$j] == $maxLen[$i+1][$j]) $i++;
			else $j++;
		}
	}
	return trim($rez);
}
	
function GetUnickStr(&$arr, &$arrUnick) {
	$s='';
	$arrUnickFlip = array_flip($arrUnick);
	foreach($arr as $v) {
		$s .= $arrUnickFlip[$v].' ';
	}
	return trim($s);
}
	
function FromUnickToArr(&$arrStr, &$arrUnick) {
	$r = array();			
	foreach($arrStr as $v) {
		$buff   = array();
		$buff[] = $arrUnick[$v[0]];
		$buff[] = $v[1];
		$r[]    = $buff;
	}		
	return $r;
}
	
function SelDiffsStr(&$_a, &$_b, &$retA, &$retB) {
	$_longest = GetLCSAlgoritm($_a,$_b);
	$longest  = explode(" ",$_longest);	
		
	$a		  = explode(" ",$_a);		
	$b		  = explode(" ",$_b);
	$rB		  = array();
		
	$i1 = 0; $i2 = 0;
	for($i=0, $iters = count($b); $i<$iters; $i++) {
		$simbol = array();
		if(isset($longest[$i1]) && $longest[$i1] == $b[$i2]) {
			$simbol[] = $longest[$i1];
			$simbol[] = "*";
			$rB[] 	  = $simbol;
			$i1++;
			$i2++;
		}
		else {
			$simbol[] = $b[$i2];
			$simbol[] = "+";
			$rB[] 	  = $simbol;
			$i2++;
		}
	}		
	$retB = $rB;
	
	$i1 = 0; $i2 = 0;
	for($i=0,$iters = count($a); $i<$iters; $i++) {
		$simbol = array();
		if(isset($longest[$i1]) && $longest[$i1] == $a[$i2]) {
			$simbol[] = $longest[$i1];
			$simbol[] = "*";
			$rA[] 	  = $simbol;
			$i1++;
			$i2++;
		}
		else {
			$simbol[] = $a[$i2];
			$simbol[] = "-";
			$rA[] 	  = $simbol;
			$i2++;
		}
	}		
	$retA = $rA;
}
	
function SelDiffsText(&$aText, &$bText, &$retAText, &$retBText) {		
	$arrA = str_replace("\r","",$aText);
	$arrB = str_replace("\r","",$bText);
	$arrA = explode("\n",$arrA);		
	$arrB = explode("\n",$arrB);
	$unickTable = array_unique(array_merge($arrA,$arrB));
	
	$strA = GetUnickStr($arrA,$unickTable);
	$strB = GetUnickStr($arrB,$unickTable);
	
	SelDiffsStr($strA,$strB,$retA,$retB);
	$retAText = FromUnickToArr($retA,$unickTable);
	$retBText = FromUnickToArr($retB,$unickTable);
}
		
function SelDiffsColor(&$rdyAText, &$rdyBText, &$strRetA, &$strRetB) {
	$strRetA = "";
	$strRetB = "";

	foreach($rdyAText as $v) {
		if($v[1] == "+") 		$strRetA.=''.$v[0].'';
		elseif($v[1] == '-') 	$strRetA.=''.$v[0].'';
		elseif($v[1] == 'm')	$strRetA.=''.$v[0].'';
		elseif($v[1] == '*')	$strRetA.=$v[0];
	}

	foreach($rdyBText as $v) {
		if($v[1] == "+")		$strRetB.=''.$v[0].'';
		elseif($v[1] == '-')	$strRetB.=''.$v[0].'';
		elseif($v[1] == 'm')	$strRetB.=''.$v[0].'';
		elseif($v[1] == '*')	$strRetB.=$v[0];
	}
}
	
function MergeInsertAndDelete(&$rdyAText, &$rdyBText) {
	$max = count($rdyAText)>count($rdyBText)?count($rdyAText):count($rdyBText);
		
	for($i1=0,$i2=0; $i1<$max && $i2<$max; ) 	{
		if($rdyAText[$i1][1]=="-" && $rdyBText[$i2][1]=="+" && $rdyBText[$i2][0]!="") {
			$rdyAText[$i1][1]="*";
			$rdyBText[$i2][1]="m";
		}
		elseif($rdyAText[$i1][1]!="-" && $rdyBText[$i2][1]=="+") $i2++;
		elseif($rdyAText[$i1][1]=="-" && $rdyBText[$i2][1]!="+") $i1++;
		
		$i1++;
		$i2++;
	}
}

// ***********************************************************
// 					Main function
// ***********************************************************
// string  $sA, $sB 	= 	strings where try find differences
// string  $retA, $retB	=	strings for return result of work
function SelectedDiffs(&$sA, &$sB, &$retA, &$retB) {
	SelDiffsText($sA,$sB,$retAText,$retBText);
	MergeInsertAndDelete($retAText,$retBText);
	SelDiffsColor($retAText,$retBText,$retA,$retB);
}

функции:

  • GetUnickStr(&$arr, &$arrUnick)
    вход : массив слов текста, указатель на уникальную таблицу инлентификаторов (которую получаем ф-ей GetUnickTable)
    выход: строка состаящая из индентификаторов взятых из таблицы уникальных индентификаторов
    (ПУНКТ 2 получаем уникальные строки)
  • FromUnickToArr(&$arrStr, &$arrUnick)
    вход : массив представляющий уникальную строку ($arrStr), массив уникальной таблицы индентификаторов ($arrUnick)
    выход: массив являющийся обычным текстом.
  • GetLCSAlgoritm(&$_a, &$_b)
    ф-я реализующая Алгоритм выделения наибольшей общей последовательности
    вход : строка уникальный индентификаторов представляющая 1-ый текст($_a) и 2-ой текст ($_b)
    выход: строка уникальных индентификаторов являющаяеся наибольшей общей последовательностью
    (ПУНКТ 3 находим наибольшую общую последовательность)
    В алгоритме строка разбивается построчно:
    текст 1:
    aaa bbb ccc == 1
    ddd eee == 2
    fff ggg == 3
    а при перобразовании в строку индентификаторов цифры разделяюся пробелами : '1 2 3'
  • SelDiffsStr(&$_a, &$_b, &$retA, &$retB)
    ф-я выдления изменений в строках индентификаторов
    вход : $_a, $_b - строки индентификатров представляющие соответсвенно текстНачальный и текстИзмененный, $retA, $retB - указатели куда нужно вернуть результат - строки индентификаторов $_a и $_b только уже с выделениями
    выход: выход организуется путем передачи указателей на параметры в которых сохранить результат
  • SelDiffsColor(&$rdyAText, &$rdyBText, &$strRetA, &$strRetB)
    ф-я предназначена для выделения цветом (удаленный текст, вставленный текст, измененный текст)
  • MergeInsertAndDelete(&$rdyAText, &$rdyBText)
    как говорилось в ПУНКТЕ 4, когда мы встретили ситуацию 4 8 5 - можно было либо сразу выделить текст как изменненый, либо так, что в 1-ой строке он был удален, а во второй вставлен, так вот у меня 2-ой вариант и потому я использую эту ф-ю, чтобы найти такие ситуации и выделить текст как измененный.
  • SelDiffsText(& $aText, & $bText, & $retAText, & $retBText)
    ф-я выделения изменений в строках только уже текстов, а не индентификаторов, т.е. эта ф-я получает на вход тексты Начальный и Измененный, сама создает уникальную таблицу, переводит эти тексты в строки индентификаторов вызывает ф-ю SelDiffsText - для выделения различий в индентификаторах, а потом для получившихся результатов вызывает ф-ю FromUnickToArr при помощи которой строки индентификаторов переводит в обычный выделенный текст
  • SelectedDiffs(& $sA, & $sB, & $retA, & $retB)
    главная ф-я которая объединяет все выше
    вход:
    $sA - текст Начальный
    $sB - текст Измененный
    $retA - указатель куда надо вернуть строку с выделенным цветами Начальным текстом
    $retB - указатель куда надо вернуть строку с выделенный цветами Измененным текстом
    выход: выход организуется путем передачи указателей на параметры в которых сохранить результат


пример:

начальный текстизмененный текст
ф-я выделения изменений
в строках только уже текстов,
а не индентификаторов,
т.е. эта ф-я получает на вход тексты 
Начальный и Измененный,
сама создает уникальную таблицу,
переводит эти тексты
ф-я выдiления изменений
в строках только уже текстов,
а не индентификаторов,
это новая строка
т.е. эта ф-я получает на вход тексты 
сама создает уникальную таблицу,
переводит тексты


Алгоритм выделения наибольшей общей последовательности можно найти тут: wikipedia - Алгоритм

Михаил Зайцев



Комментарии:



sdsadasd    11.03.11 16:19

adasdasda






Если вам помогла или просто понравилась эта статья вы можете отблагодарить автора кликнув по рекламе. Спасибо!





комментировать:
прежде чем писать комментарий убедитесь, пожалуйста, что он не попадает в нижеследующие категории:
  • не стоит писать "я чайник, помогите ..." или "я начинающий, объясните ..." - уважайте себя! может вы и новичок, но ведь не тупой же! сами вполне способны во всем разобраться, тем более, что материал здесь изложен весьма доступно

  • не пишите вопросы типа "как мне сделать такую же менюху как наверху?", "куда вставлять этот код?", "как устроен интернет?" и т.д. - уважайте время автора!

  • комментарии вида "все, что здесь написано - бред" будут оставлены только если будут подписаны "я такой-то, разработчик сайта такого-то с посещаемостью 1000 хостов в день" и т.п. Если вы не согласны с чем-то - обоснуйте, напишите как правильно, а писать простые ругательства не надо, это не забор

прямые оскорбления кого бы то ни было будут удалятся!
здоровая критика приветствуется!



от кого:  

security picture