Porównywacz Tekstów
Porównaj dwa teksty obok siebie i wyróżnij różnice linia po linii
Tekst A
Tekst B
Czym jest Text Diff?
Text diff (skrót od 'difference', czyli różnica) to wynik porównania dwóch bloków tekstu z oznaczeniem linii dodanych, usuniętych lub niezmienionych. Koncepcja wywodzi się z narzędzia Unix diff, wydanego po raz pierwszy w 1974 roku w ramach Version 5 Unix. Dziś diff jest podstawą systemów kontroli wersji, takich jak Git — każdy commit przechowuje diff zamiast pełnej kopii każdego pliku.
Algorytm diff wyznacza Najdłuższy Wspólny Podciąg (LCS) między dwiema sekwencjami linii. Linie należące do LCS są oznaczane jako niezmienione. Linie obecne w tekście oryginalnym, ale nie w LCS, są oznaczane jako usunięte. Linie obecne w tekście zmodyfikowanym, ale nie w LCS, są oznaczane jako dodane. Wynikiem jest minimalny zestaw zmian potrzebnych do przekształcenia jednego tekstu w drugi.
Wynik diff może mieć kilka formatów. Unified diff (domyślny format dla git diff) poprzedza usunięte linie minusem, a dodane linie plusem. Diff side-by-side wyświetla oba teksty w równoległych kolumnach. To narzędzie stosuje porównanie linia po linii z wyjściem kodowanym kolorami: zielony dla dodań, czerwony dla usunięć, neutralny dla linii niezmiennych. Niezmienione linie są domyślnie wyświetlane bez prefiksu, ale można je ukryć, aby skupić się wyłącznie na zmianach.
Dlaczego warto używać tego narzędzia?
Porównywanie tekstu w terminalu wymaga instalacji narzędzi diff i obsługi opcji wiersza poleceń. Narzędzie diff w przeglądarce całkowicie eliminuje to tarcie.
Zastosowania narzędzia Text Diff
Porównanie formatów wyjścia diff
Narzędzia diff produkują wyjście w kilku formatach. Poniższa tabela podsumowuje najpopularniejsze z nich, źródła ich generowania oraz sytuacje, w których każdy jest przydatny.
| Format | Narzędzie / Źródło | Opis |
|---|---|---|
| Unified diff | diff -u / git diff | Prefixes lines with + / - / space; includes @@ hunk headers |
| Side-by-side | diff -y / sdiff | Two columns, changed lines aligned horizontally |
| Context diff | diff -c | Shows changed lines with surrounding context, marked with ! / + / - |
| HTML diff | Python difflib | Color-coded HTML table with inline change highlights |
| JSON Patch | RFC 6902 | Array of add/remove/replace operations on a JSON document |
Jak działa diff linii: algorytm LCS
Większość narzędzi do porównywania linii, w tym to narzędzie, używa algorytmu Najdłuższego Wspólnego Podciągu (LCS). LCS wyznacza największy zbiór linii, które pojawiają się w obu tekstach w tej samej kolejności względnej, bez wymagania ciągłości. Linie nie należące do LCS stanowią właściwe różnice.
Standardowy algorytm LCS używa programowania dynamicznego i działa w czasie O(m x n), gdzie m i n to liczby linii obu tekstów. Dla dużych plików zoptymalizowane warianty, takie jak algorytm Myersa (używany przez Git), redukują złożoność do O(n + d^2), gdzie d to liczba różnic — co sprawia, że jest szybki, gdy większość linii jest wspólna.
Przykłady kodu
Implementacje porównywania tekstu linia po linii w JavaScript, Pythonie, Go i wierszu poleceń. Każdy przykład produkuje wyjście w stylu unified diff.
// Line-by-line diff using the LCS algorithm
function diffLines(a, b) {
const linesA = a.split('\n')
const linesB = b.split('\n')
// Build LCS table
const m = linesA.length, n = linesB.length
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0))
for (let i = 1; i <= m; i++)
for (let j = 1; j <= n; j++)
dp[i][j] = linesA[i-1] === linesB[j-1]
? dp[i-1][j-1] + 1
: Math.max(dp[i-1][j], dp[i][j-1])
// Backtrack to produce diff
const result = []
let i = m, j = n
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && linesA[i-1] === linesB[j-1]) {
result.unshift({ type: 'equal', text: linesA[i-1] }); i--; j--
} else if (j > 0 && (i === 0 || dp[i][j-1] >= dp[i-1][j])) {
result.unshift({ type: 'add', text: linesB[j-1] }); j--
} else {
result.unshift({ type: 'remove', text: linesA[i-1] }); i--
}
}
return result
}
const diff = diffLines("alpha\nbeta\ngamma", "alpha\nbeta changed\ngamma\ndelta")
// → [
// { type: 'equal', text: 'alpha' },
// { type: 'remove', text: 'beta' },
// { type: 'add', text: 'beta changed' },
// { type: 'equal', text: 'gamma' },
// { type: 'add', text: 'delta' }
// ]import difflib
text_a = """alpha
beta
gamma""".splitlines()
text_b = """alpha
beta changed
gamma
delta""".splitlines()
# Unified diff (same format as git diff)
for line in difflib.unified_diff(text_a, text_b, fromfile='a.txt', tofile='b.txt', lineterm=''):
print(line)
# --- a.txt
# +++ b.txt
# @@ -1,3 +1,4 @@
# alpha
# -beta
# +beta changed
# gamma
# +delta
# HTML side-by-side diff
d = difflib.HtmlDiff()
html = d.make_file(text_a, text_b, fromdesc='Original', todesc='Modified')package main
import (
"fmt"
"strings"
)
// Minimal LCS-based line diff
func diffLines(a, b string) {
la := strings.Split(a, "\n")
lb := strings.Split(b, "\n")
m, n := len(la), len(lb)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if la[i-1] == lb[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
} else if dp[i-1][j] >= dp[i][j-1] {
dp[i][j] = dp[i-1][j]
} else {
dp[i][j] = dp[i][j-1]
}
}
}
var result []string
i, j := m, n
for i > 0 || j > 0 {
if i > 0 && j > 0 && la[i-1] == lb[j-1] {
result = append([]string{" " + la[i-1]}, result...)
i--; j--
} else if j > 0 && (i == 0 || dp[i][j-1] >= dp[i-1][j]) {
result = append([]string{"+" + lb[j-1]}, result...)
j--
} else {
result = append([]string{"-" + la[i-1]}, result...)
i--
}
}
for _, line := range result {
fmt.Println(line)
}
}
// Output:
// alpha
// -beta
// +beta changed
// gamma
// +delta# Compare two files with unified diff (3 lines of context) diff -u original.txt modified.txt # Git diff between working tree and last commit git diff HEAD -- file.txt # Git diff between two branches git diff main..feature -- src/ # Side-by-side diff in the terminal diff -y --width=120 original.txt modified.txt # Color-coded diff (requires colordiff) diff -u original.txt modified.txt | colordiff