เปรียบเทียบข้อความ
เปรียบเทียบข้อความสองอย่างแบบเคียงข้างและเน้นความแตกต่างบรรทัดต่อบรรทัด
ข้อความ A
ข้อความ B
Text Diff คืออะไร?
Text diff (ย่อมาจาก "difference") คือผลลัพธ์ของการเปรียบเทียบข้อความสองบล็อกและระบุว่าบรรทัดใดถูกเพิ่ม ลบ หรือคงเดิม แนวคิดนี้มีต้นกำเนิดจากยูทิลิตี Unix diff ที่เปิดตัวครั้งแรกในปี 1974 ในฐานะส่วนหนึ่งของ Version 5 Unix ปัจจุบัน text diff เป็นรากฐานของระบบควบคุมเวอร์ชันอย่าง Git ซึ่งทุก commit เก็บ diff แทนที่จะเก็บสำเนาเต็มของแต่ละไฟล์
อัลกอริทึม diff ค้นหา Longest Common Subsequence (LCS) ระหว่างลำดับบรรทัดสองชุด บรรทัดที่อยู่ใน LCS จะถูกทำเครื่องหมายว่าไม่เปลี่ยนแปลง บรรทัดในข้อความต้นฉบับที่ไม่อยู่ใน LCS จะถูกทำเครื่องหมายว่าลบออก บรรทัดในข้อความที่แก้ไขที่ไม่อยู่ใน LCS จะถูกทำเครื่องหมายว่าเพิ่มใหม่ ผลลัพธ์คือชุดการเปลี่ยนแปลงขั้นต่ำที่จำเป็นเพื่อแปลงข้อความหนึ่งไปเป็นอีกข้อความหนึ่ง
ผลลัพธ์ diff มีหลายรูปแบบ Unified diff (ค่าเริ่มต้นสำหรับ git diff) ใส่คำนำหน้าบรรทัดที่ลบด้วยเครื่องหมายลบและบรรทัดที่เพิ่มด้วยเครื่องหมายบวก Side-by-side diff จัดเรียงข้อความทั้งสองในคอลัมน์คู่ขนาน เครื่องมือนี้ใช้การเปรียบเทียบบรรทัดต่อบรรทัดพร้อมสีที่กำหนด: สีเขียวสำหรับบรรทัดที่เพิ่ม สีแดงสำหรับบรรทัดที่ลบ และสีกลางสำหรับบรรทัดที่ไม่เปลี่ยนแปลง บรรทัดที่ไม่เปลี่ยนแปลงจะแสดงโดยไม่มีคำนำหน้าตามค่าเริ่มต้น แต่สามารถซ่อนได้เพื่อโฟกัสเฉพาะสิ่งที่เปลี่ยนแปลง
ทำไมต้องใช้เครื่องมือ Text Diff ออนไลน์?
การเปรียบเทียบข้อความในเทอร์มินัลต้องติดตั้งยูทิลิตี diff และจัดการกับ command-line flags เครื่องมือ diff บนเบราว์เซอร์ขจัดความยุ่งยากนั้นทั้งหมด
กรณีการใช้งาน Text 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 |
การทำงานของ Line Diff: อัลกอริทึม LCS
เครื่องมือ line-diff ส่วนใหญ่ รวมถึงเครื่องมือนี้ ใช้อัลกอริทึม Longest Common Subsequence (LCS) LCS ค้นหาชุดบรรทัดที่ใหญ่ที่สุดที่ปรากฏในข้อความทั้งสองในลำดับสัมพัทธ์เดียวกัน โดยไม่จำเป็นต้องต่อเนื่องกัน บรรทัดที่ไม่อยู่ใน LCS คือความแตกต่างที่แท้จริง
อัลกอริทึม LCS มาตรฐานใช้ dynamic programming และทำงานใน O(m x n) โดยที่ m และ n คือจำนวนบรรทัดของข้อความทั้งสอง สำหรับไฟล์ขนาดใหญ่ ตัวแปรที่ปรับปรุงเช่น Myers' diff algorithm (ที่ใช้โดย Git) ลดความซับซ้อนเป็น O(n + d^2) โดยที่ d คือจำนวนความแตกต่าง ทำให้เร็วเมื่อส่วนใหญ่ของบรรทัดเหมือนกัน
ตัวอย่างโค้ด
การ implement การเปรียบเทียบข้อความบรรทัดต่อบรรทัดใน JavaScript, Python, Go และ command line แต่ละตัวอย่างสร้างผลลัพธ์ในรูปแบบ unified-style 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