<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://bukvica.org/w/index.php?action=history&amp;feed=atom&amp;title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%3ADiff</id>
	<title>Модуль:Diff - История изменений</title>
	<link rel="self" type="application/atom+xml" href="https://bukvica.org/w/index.php?action=history&amp;feed=atom&amp;title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%3ADiff"/>
	<link rel="alternate" type="text/html" href="https://bukvica.org/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:Diff&amp;action=history"/>
	<updated>2026-04-24T21:10:55Z</updated>
	<subtitle>История изменений этой страницы в вики</subtitle>
	<generator>MediaWiki 1.43.8</generator>
	<entry>
		<id>https://bukvica.org/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:Diff&amp;diff=238727&amp;oldid=prev</id>
		<title>Karaby: 1 версия импортирована</title>
		<link rel="alternate" type="text/html" href="https://bukvica.org/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:Diff&amp;diff=238727&amp;oldid=prev"/>
		<updated>2025-07-27T15:38:10Z</updated>

		<summary type="html">&lt;p&gt;1 версия импортирована&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;ru&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Предыдущая версия&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Версия от 15:38, 27 июля 2025&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;ru&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(нет различий)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Karaby</name></author>
	</entry>
	<entry>
		<id>https://bukvica.org/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:Diff&amp;diff=238726&amp;oldid=prev</id>
		<title>Буквица&gt;D6194c-1cc: D6194c-1cc переименовал страницу Модуль:Песочница/D6194c-1cc/Diff в Модуль:Diff без оставления перенаправления: Публикация модуля из черновика</title>
		<link rel="alternate" type="text/html" href="https://bukvica.org/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:Diff&amp;diff=238726&amp;oldid=prev"/>
		<updated>2023-07-15T21:34:27Z</updated>

		<summary type="html">&lt;p&gt;D6194c-1cc переименовал страницу &lt;a href=&quot;/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:%D0%9F%D0%B5%D1%81%D0%BE%D1%87%D0%BD%D0%B8%D1%86%D0%B0/D6194c-1cc/Diff&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;Модуль:Песочница/D6194c-1cc/Diff (страница не существует)&quot;&gt;Модуль:Песочница/D6194c-1cc/Diff&lt;/a&gt; в &lt;a href=&quot;/wiki/%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:Diff&quot; title=&quot;Модуль:Diff&quot;&gt;Модуль:Diff&lt;/a&gt; без оставления перенаправления: Публикация модуля из черновика&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Новая страница&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local p = {}&lt;br /&gt;
&lt;br /&gt;
-- TODO: Провести эксперимент с получением таблицы из символов из строки, проверить&lt;br /&gt;
-- будет, ли это быстрее, чем сравнение подстрок. Если представление ustring именно UTF-8,&lt;br /&gt;
-- то работа с таблицей может быть в теории быстрее (нет необходимости каждый раз обходить&lt;br /&gt;
-- всю строку для вычисления позиции символа).&lt;br /&gt;
-- Альтернативно подумать над алгоритмом побайтового сравнения, это должно быть быстрее.&lt;br /&gt;
&lt;br /&gt;
-- TODO: Проверить по производительности в strLongestCommonRange() смену большей&lt;br /&gt;
-- и меньшей строк местами.&lt;br /&gt;
&lt;br /&gt;
-- TODO: Использовать find с указанием индекса начала поиска в strLongestCommonRange.&lt;br /&gt;
&lt;br /&gt;
function p.strLongestCommonRange(s1, s2, minWindowSize)&lt;br /&gt;
	-- TODO: Начало и длина вместо диапазонов&lt;br /&gt;
	if not minWindowSize then&lt;br /&gt;
		minWindowSize = 0&lt;br /&gt;
	end&lt;br /&gt;
	local longestStart1 = 0&lt;br /&gt;
	local longestEnd1 = 0&lt;br /&gt;
	local longestStart2 = 0&lt;br /&gt;
	local longestEnd2 = 0&lt;br /&gt;
	local currStart = 1&lt;br /&gt;
	local currEnd = 1 + minWindowSize&lt;br /&gt;
	&lt;br /&gt;
	local s1Len = mw.ustring.len(s1)&lt;br /&gt;
	local s2Len = mw.ustring.len(s2)&lt;br /&gt;
&lt;br /&gt;
	while currEnd &amp;lt;= s1Len do&lt;br /&gt;
		local found = false&lt;br /&gt;
		local subS1&lt;br /&gt;
		local subS2&lt;br /&gt;
		local i = 1&lt;br /&gt;
&lt;br /&gt;
		subS1 = mw.ustring.sub(s1, currStart, currEnd)&lt;br /&gt;
		while i + (currEnd - currStart) &amp;lt;= s2Len do&lt;br /&gt;
			if not found then&lt;br /&gt;
				subS2 = mw.ustring.sub(s2, i, i + (currEnd - currStart))&lt;br /&gt;
				if subS1 == subS2 then&lt;br /&gt;
					found = true&lt;br /&gt;
					currEnd = currEnd + 1&lt;br /&gt;
					if currEnd &amp;gt; s1Len then&lt;br /&gt;
						break&lt;br /&gt;
					end&lt;br /&gt;
				else&lt;br /&gt;
					i = i + 1&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				local currEnd2 = i + currEnd - currStart&lt;br /&gt;
				if mw.ustring.sub(s1, currEnd, currEnd) == mw.ustring.sub(s2, currEnd2, currEnd2) then&lt;br /&gt;
					currEnd = currEnd + 1&lt;br /&gt;
					if currEnd &amp;gt; s1Len then&lt;br /&gt;
						break&lt;br /&gt;
					end&lt;br /&gt;
				else&lt;br /&gt;
					if currEnd - 1 - currStart &amp;gt; s1Len / 2 then&lt;br /&gt;
						return { currStart, currEnd - 1 }, { i, i + currEnd - 1 - currStart }&lt;br /&gt;
					end&lt;br /&gt;
					subS1 = mw.ustring.sub(s1, currStart, currEnd)&lt;br /&gt;
					if currEnd - 1 - currStart &amp;gt; longestEnd1 - longestStart1 or longestStart1 == 0 then&lt;br /&gt;
						longestStart1 = currStart&lt;br /&gt;
						longestEnd1 = currEnd - 1&lt;br /&gt;
						longestStart2 = i&lt;br /&gt;
						longestEnd2 = i + (longestEnd1 - longestStart1)&lt;br /&gt;
					end&lt;br /&gt;
					found = false&lt;br /&gt;
					i = i + 1&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if found then&lt;br /&gt;
			if currEnd - 1 - currStart &amp;gt; longestEnd1 - longestStart1 or longestStart1 == 0 then&lt;br /&gt;
				longestStart1 = currStart&lt;br /&gt;
				longestEnd1 = currEnd - 1&lt;br /&gt;
				longestStart2 = i&lt;br /&gt;
				longestEnd2 = i + (longestEnd1 - longestStart1)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		currStart = currStart + 1&lt;br /&gt;
		currEnd = currEnd + 1&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if longestStart1 == 0 then&lt;br /&gt;
		return nil, nil&lt;br /&gt;
	end&lt;br /&gt;
	return { longestStart1, longestEnd1 }, { longestStart2, longestEnd2 }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.strCommonRangesApprox(s1, s2, limit, s1Offset, s2Offset, ranges)&lt;br /&gt;
	ranges = ranges or {}&lt;br /&gt;
	s1Offset = s1Offset or 0&lt;br /&gt;
	s2Offset = s2Offset or 0&lt;br /&gt;
&lt;br /&gt;
 	if s1 == &amp;#039;&amp;#039; or s2 == &amp;#039;&amp;#039; then&lt;br /&gt;
 		return ranges&lt;br /&gt;
 	end&lt;br /&gt;
&lt;br /&gt;
	local d1, d2 = p.strLongestCommonRange(s1, s2, limit or 0)&lt;br /&gt;
	if d1 == nil then&lt;br /&gt;
		return ranges&lt;br /&gt;
	end&lt;br /&gt;
	if limit and d1[2] - d1[1] &amp;lt;= limit and d2[2] - d2[1] &amp;lt;= limit then&lt;br /&gt;
		return ranges&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if d1[1] &amp;gt; 1 then&lt;br /&gt;
		p.strCommonRangesApprox(&lt;br /&gt;
			mw.ustring.sub(s1, 1, d1[1] - 1),&lt;br /&gt;
			mw.ustring.sub(s2, 1, d2[1] - 1),&lt;br /&gt;
			limit,&lt;br /&gt;
			s1Offset,&lt;br /&gt;
			s2Offset,&lt;br /&gt;
			ranges)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	d1[1] = d1[1] + s1Offset&lt;br /&gt;
	d1[2] = d1[2] + s1Offset&lt;br /&gt;
&lt;br /&gt;
	d2[1] = d2[1] + s2Offset&lt;br /&gt;
	d2[2] = d2[2] + s2Offset&lt;br /&gt;
	&lt;br /&gt;
	table.insert(ranges, { d1, d2 })&lt;br /&gt;
&lt;br /&gt;
	if d1[2] - s1Offset &amp;lt; mw.ustring.len(s1) and d2[2] - s2Offset &amp;lt; mw.ustring.len(s2) then&lt;br /&gt;
		p.strCommonRangesApprox(&lt;br /&gt;
			mw.ustring.sub(s1, d1[2] + 1, mw.ustring.len(s1)),&lt;br /&gt;
			mw.ustring.sub(s2, d2[2] + 1, mw.ustring.len(s2)),&lt;br /&gt;
			limit,&lt;br /&gt;
			d1[2], d2[2],&lt;br /&gt;
			ranges)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return ranges&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.strCommonRanges(s1, s2, s1Offset, s2Offset, ranges)&lt;br /&gt;
	return p.strCommonRangesApprox(s1, s2, nil, s1Offset, s2Offset, ranges)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function tryGetWholeDiff(s1, s2)&lt;br /&gt;
	local s1Set = (s1 and s1 ~= &amp;#039;&amp;#039;)&lt;br /&gt;
	local s2Set = (s2 and s2 ~= &amp;#039;&amp;#039;)&lt;br /&gt;
	if s1Set and not s2Set then&lt;br /&gt;
		return {&lt;br /&gt;
			{&lt;br /&gt;
				{ 1, mw.ustring.len(s1) },&lt;br /&gt;
				{ 1, 0 },&lt;br /&gt;
			},&lt;br /&gt;
		}&lt;br /&gt;
	elseif not s1Set and s2Set then&lt;br /&gt;
		return {&lt;br /&gt;
			{&lt;br /&gt;
				{ 1, 0 },&lt;br /&gt;
				{ 1, mw.ustring.len(s2) },&lt;br /&gt;
			},&lt;br /&gt;
		}&lt;br /&gt;
	elseif (not s1Set and not s2Set) or (s1 == s2) then&lt;br /&gt;
		return {}&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function strDiffRangesNonNil(s1, s2, limit)&lt;br /&gt;
	local commonSubstrings = p.strCommonRangesApprox(s1, s2, limit)&lt;br /&gt;
	if table.getn(commonSubstrings) == 0 then&lt;br /&gt;
		return {&lt;br /&gt;
			{&lt;br /&gt;
				{ 1, mw.ustring.len(s1) },&lt;br /&gt;
				{ 1, mw.ustring.len(s2) },&lt;br /&gt;
			},&lt;br /&gt;
		}&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local diffs = {}&lt;br /&gt;
&lt;br /&gt;
	local curr = commonSubstrings[1]&lt;br /&gt;
	local last1 = 1&lt;br /&gt;
	local last2 = 1&lt;br /&gt;
	if curr[1][1] - last1 &amp;gt; 0 or curr[2][1] - last2 &amp;gt; 0 then&lt;br /&gt;
		table.insert(diffs, {&lt;br /&gt;
			{ last1, curr[1][1] - last1 },&lt;br /&gt;
			{ last2, curr[2][1] - last2 },&lt;br /&gt;
		})&lt;br /&gt;
	end&lt;br /&gt;
	last1 = curr[1][2]&lt;br /&gt;
	last2 = curr[2][2]&lt;br /&gt;
&lt;br /&gt;
	for i = 2, table.getn(commonSubstrings) do&lt;br /&gt;
		curr = commonSubstrings[i]&lt;br /&gt;
		if curr[1][1] - last1 &amp;gt; 0 and curr[2][1] - last2 &amp;gt; 0 then&lt;br /&gt;
			if curr[1][1] - last1 &amp;gt; 0 then&lt;br /&gt;
				last1 = last1 + 1&lt;br /&gt;
			end&lt;br /&gt;
			if curr[2][1] - last2 &amp;gt; 0 then&lt;br /&gt;
				last2 = last2 + 1&lt;br /&gt;
			end&lt;br /&gt;
			table.insert(diffs, {&lt;br /&gt;
				{ last1, curr[1][1] - last1 },&lt;br /&gt;
				{ last2, curr[2][1] - last2 },&lt;br /&gt;
			})&lt;br /&gt;
		end&lt;br /&gt;
		last1 = curr[1][2]&lt;br /&gt;
		last2 = curr[2][2]&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if mw.ustring.len(s1) - last1 &amp;gt; 0 or mw.ustring.len(s2) - last2 &amp;gt; 0 then&lt;br /&gt;
		table.insert(diffs, {&lt;br /&gt;
			{ last1 + 1, mw.ustring.len(s1) - last1 },&lt;br /&gt;
			{ last2 + 1, mw.ustring.len(s2) - last2 },&lt;br /&gt;
		})&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return diffs&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.strDiffRanges(s1, s2, limit)&lt;br /&gt;
	local diffs = tryGetWholeDiff(s1, s2)&lt;br /&gt;
	if diffs then&lt;br /&gt;
		return diffs&lt;br /&gt;
	else&lt;br /&gt;
		diffs = {}&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return strDiffRangesNonNil(s1, s2, limit)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.findCommonFromStart(s1, s2)&lt;br /&gt;
	if mw.ustring.sub(s1, 1, 1) ~= mw.ustring.sub(s2, 1, 1) then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local s1Len = mw.ustring.len(s1)&lt;br /&gt;
	local s2Len = mw.ustring.len(s2)&lt;br /&gt;
	local sLen&lt;br /&gt;
	if s1Len &amp;lt; s2Len then&lt;br /&gt;
		sLen = s1Len&lt;br /&gt;
	else&lt;br /&gt;
		sLen = s2Len&lt;br /&gt;
	end&lt;br /&gt;
	local i = 1&lt;br /&gt;
	local d = math.floor(sLen / 10)&lt;br /&gt;
	if d &amp;lt; 1 then&lt;br /&gt;
		d = 1&lt;br /&gt;
	end&lt;br /&gt;
	while i &amp;lt;= sLen do&lt;br /&gt;
		local s1Start = mw.ustring.sub(s1, i, i + d)&lt;br /&gt;
		local s2Start = mw.ustring.sub(s2, i, i + d)&lt;br /&gt;
		if s1Start ~= s2Start then&lt;br /&gt;
			for j = 1, d do&lt;br /&gt;
				if mw.ustring.sub(s1Start, j, j) ~= mw.ustring.sub(s2Start, j, j) then&lt;br /&gt;
					return i + j - 1 - 1&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		i = i + d&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return sLen&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.findCommonFromEnd(s1, s2)&lt;br /&gt;
	local s1Len = mw.ustring.len(s1)&lt;br /&gt;
	local s2Len = mw.ustring.len(s2)&lt;br /&gt;
	if mw.ustring.sub(s1, s1Len, s1Len) ~= mw.ustring.sub(s2, s2Len, s2Len) then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local sLen&lt;br /&gt;
	if s1Len &amp;lt; s2Len then&lt;br /&gt;
		sLen = s1Len&lt;br /&gt;
	else&lt;br /&gt;
		sLen = s2Len&lt;br /&gt;
	end&lt;br /&gt;
	local i = 0&lt;br /&gt;
	local d = math.floor(sLen / 10)&lt;br /&gt;
	if d &amp;lt; 1 then&lt;br /&gt;
		d = 1&lt;br /&gt;
	end&lt;br /&gt;
	while i &amp;lt;= sLen do&lt;br /&gt;
		local s1Start = mw.ustring.sub(s1, s1Len - (i + d), s1Len - i)&lt;br /&gt;
		local s2Start = mw.ustring.sub(s2, s2Len - (i + d), s2Len - i)&lt;br /&gt;
		if s1Start ~= s2Start then&lt;br /&gt;
			for j = d, 1, -1 do&lt;br /&gt;
				if mw.ustring.sub(s1Start, j, j) ~= mw.ustring.sub(s2Start, j, j) then&lt;br /&gt;
					return i + d - j + 1&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		i = i + d&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return sLen&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Symbol from the string in Lua can be obtained only by the sub function,&lt;br /&gt;
-- which creates new string with global deduplication. Symbol by symbol&lt;br /&gt;
-- comparison is very slow. Diff algorithm is fast on small strings,&lt;br /&gt;
-- but on large strings it becomes extremely slowly in Lua.&lt;br /&gt;
-- On large strings, approximations are made to ignore minor similarities.&lt;br /&gt;
function p.fastStrDiffRanges(s1, s2, accuracyMaxLen)&lt;br /&gt;
	local diffs = tryGetWholeDiff(s1, s2)&lt;br /&gt;
	if diffs then&lt;br /&gt;
		return diffs&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local limit&lt;br /&gt;
	local totalLen = s1:len() + s2:len()&lt;br /&gt;
&lt;br /&gt;
	local startIndex&lt;br /&gt;
	&lt;br /&gt;
	if not accuracyMaxLen then&lt;br /&gt;
		accuracyMaxLen = 300&lt;br /&gt;
	end&lt;br /&gt;
	if totalLen &amp;gt; accuracyMaxLen then&lt;br /&gt;
		startIndex = p.findCommonFromStart(s1, s2)&lt;br /&gt;
		local endIndex = p.findCommonFromEnd(s1, s2)&lt;br /&gt;
		if startIndex then&lt;br /&gt;
			startIndex = startIndex + 1&lt;br /&gt;
		end&lt;br /&gt;
		if startIndex or endIndex then&lt;br /&gt;
			if not endIndex then&lt;br /&gt;
				endIndex = 0&lt;br /&gt;
			end&lt;br /&gt;
			s1 = mw.ustring.sub(s1, startIndex or 1, mw.ustring.len(s1) - endIndex)&lt;br /&gt;
			s2 = mw.ustring.sub(s2, startIndex or 1, mw.ustring.len(s2) - endIndex)&lt;br /&gt;
		end&lt;br /&gt;
		totalLen = s1:len() + s2:len()&lt;br /&gt;
		if totalLen &amp;gt; accuracyMaxLen then&lt;br /&gt;
			limit = totalLen - accuracyMaxLen&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	diffs = p.strDiffRanges(s1, s2, limit)&lt;br /&gt;
	if startIndex then&lt;br /&gt;
		for _, diff in ipairs(diffs) do&lt;br /&gt;
			diff[1][1] = diff[1][1] + startIndex - 1&lt;br /&gt;
			diff[2][1] = diff[2][1] + startIndex - 1&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return diffs&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Буквица&gt;D6194c-1cc</name></author>
	</entry>
</feed>