Text Diff
Porovnejte dva texty vedle sebe a zvýrazněte rozdíly řádek po řádku
Text A
Text B
Co je text diff?
Text diff (zkratka anglického slova „difference”, tedy rozdíl) je výsledek porovnání dvou bloků textu za účelem identifikace přidaných, odebraných nebo nezměněných řádků. Koncept pochází z unixového nástroje diff, který byl poprvé vydán v roce 1974 jako součást Version 5 Unix. Dnes je text diff základem verzovacích systémů jako Git, kde každý commit ukládá diff namísto úplné kopie každého souboru.
Diff algoritmus hledá Nejdelší společnou podposloupnost (LCS) mezi dvěma sekvencemi řádků. Řádky obsažené v LCS jsou označeny jako nezměněné. Řádky v původním textu, které v LCS chybí, jsou označeny jako odebrané. Řádky v upraveném textu, které v LCS chybí, jsou označeny jako přidané. Výsledkem je minimální sada změn potřebných k transformaci jednoho textu na druhý.
Výstup diff nástroje existuje v několika formátech. Unified diff (výchozí formát pro git diff) označuje odebrané řádky znaménkem minus a přidané řádky znaménkem plus. Side-by-side diff zobrazuje oba texty ve vedlejších sloupcích. Tento nástroj používá porovnání řádek po řádku s barevně odlišeným výstupem: zelená pro přidané, červená pro odebrané a neutrální pro nezměněné řádky. Nezměněné řádky se zobrazují bez předpony, lze je však skrýt, aby bylo vidět pouze to, co se změnilo.
Proč používat online diff nástroj?
Porovnávání textů v terminálu vyžaduje instalaci diff nástrojů a znalost přepínačů příkazové řádky. Diff nástroj v prohlížeči tuto překážku zcela odstraňuje.
Případy použití text diff nástroje
Porovnání formátů výstupu diff
Diff nástroje produkují výstup v několika formátech. Níže uvedená tabulka shrnuje nejčastěji používané formáty, co je generuje a kdy je který užitečný.
| Formát | Nástroj / Zdroj | Popis |
|---|---|---|
| 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 funguje line diff: algoritmus LCS
Většina line-diff nástrojů, včetně tohoto, používá algoritmus Nejdelší společné podposloupnosti (LCS). LCS nalezne největší sadu řádků, které se v obou textech vyskytují ve stejném relativním pořadí, aniž by musely být sousední. Řádky, které nejsou v LCS, jsou skutečnými rozdíly.
Standardní algoritmus LCS využívá dynamické programování a běží v čase O(m × n), kde m a n jsou počty řádků obou textů. Pro velké soubory optimalizované varianty jako Myersův diff algoritmus (používaný Gitem) tuto složitost snižují na O(n + d²), kde d je počet rozdílů, čímž je výpočet rychlý v případě, kdy je většina řádků sdílená.
Příklady kódu
Implementace porovnání textu řádek po řádku v JavaScriptu, Pythonu, Go a na příkazové řádce. Každý příklad produkuje výstup ve 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