مقایسهگر متن
دو متن را کنار یکدیگر مقایسه کنید و تفاوتها را خطبهخط برجسته کنید
متن الف
متن ب
مقایسه متن (Text Diff) چیست؟
مقایسه متن (Text Diff) نتیجه مقایسه دو بلوک متن و شناسایی خطهایی است که اضافه، حذف، یا بدون تغییر ماندهاند. این مفهوم از ابزار diff در Unix گرفته شده که در سال ۱۹۷۴ به عنوان بخشی از Version 5 Unix منتشر شد. امروزه، مقایسه متن ستون فقرات سیستمهای کنترل نسخه مانند Git است، جایی که هر commit یک diff ذخیره میکند نه یک نسخه کامل از هر فایل.
یک الگوریتم diff طولانیترین زیردنباله مشترک (LCS) بین دو دنباله از خطوط را پیدا میکند. خطوطی که در LCS وجود دارند به عنوان بدون تغییر علامتگذاری میشوند. خطوطی که در متن اصلی هستند اما در LCS نیستند به عنوان حذفشده علامتگذاری میشوند. خطوطی که در متن ویرایششده هستند اما در LCS نیستند به عنوان افزودهشده علامتگذاری میشوند. نتیجه حداقل مجموعه تغییرات لازم برای تبدیل یک متن به دیگری است.
خروجی diff در چند قالب ارائه میشود. unified diff (پیشفرض برای git diff) خطوط حذفشده را با علامت منفی و خطوط افزودهشده را با علامت مثبت مشخص میکند. side-by-side diff هر دو متن را در ستونهای موازی قرار میدهد. این ابزار از مقایسه خطبهخط با رنگبندی استفاده میکند: سبز برای افزودهشدهها، قرمز برای حذفشدهها، و خنثی برای خطوط بدون تغییر. خطوط بدون تغییر بهطور پیشفرض بدون پیشوند نمایش داده میشوند اما میتوان آنها را پنهان کرد تا تنها روی تغییرات تمرکز شود.
چرا از ابزار مقایسه متن آنلاین استفاده کنیم؟
مقایسه متن در ترمینال نیاز به نصب ابزارهای diff و کار با flagهای خط فرمان دارد. یک ابزار diff مبتنی بر مرورگر این اصطکاک را بهطور کامل از بین میبرد.
موارد استفاده از ابزار مقایسه متن
مقایسه قالبهای خروجی Diff
ابزارهای diff خروجی را در چندین قالب تولید میکنند. جدول زیر رایجترین آنها، آنچه آنها را تولید میکند، و زمان مفید بودن هر کدام را خلاصه میکند.
| قالب | ابزار / منبع | توضیح |
|---|---|---|
| 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 |
نحوه کار مقایسه خطوط: الگوریتم LCS
اکثر ابزارهای مقایسه خط، از جمله این ابزار، از الگوریتم طولانیترین زیردنباله مشترک (LCS) استفاده میکنند. LCS بزرگترین مجموعه خطوطی را پیدا میکند که در هر دو متن به همان ترتیب نسبی ظاهر میشوند، بدون اینکه نیازی به پیوستگی داشته باشند. خطوطی که در LCS نیستند تفاوتهای واقعی هستند.
الگوریتم استاندارد LCS از برنامهنویسی پویا استفاده میکند و در زمان O(m x n) اجرا میشود، که m و n تعداد خطوط دو متن هستند. برای فایلهای بزرگ، انواع بهینهشده مانند الگوریتم diff مایرز (Myers' diff algorithm) که توسط Git استفاده میشود، این را به O(n + d^2) کاهش میدهد که d تعداد تفاوتهاست، که باعث میشود هنگامی که بیشتر خطوط مشترک هستند سریع باشد.
نمونههای کد
پیادهسازی مقایسه متن خطبهخط در JavaScript، Python، Go و خط فرمان. هر نمونه خروجی به سبک 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