Diff: el cálculo de diferencias omnipresente
Desde la gestión de versiones de Git hasta la revisión de código, desde cambios de configuración hasta la sincronización de datos — Diff es una operación fundamental en el desarrollo de software.
| Escenario |
Tipo de Diff |
Herramienta |
| Cambios de código |
Diff por líneas |
Git |
| Comparación de configuración |
Diff por clave-valor |
Herramienta de comparación en línea |
| Datos JSON |
Diff estructurado |
json-diff |
| Documentos legales |
Diff semántico |
Herramienta especializada |
| Revisión de traducciones |
Diff por caracteres |
Comparación en línea |
I. Principios del algoritmo Myers Diff
Idea central
El algoritmo Myers busca el script de edición más corto (SES) que convierte el texto fuente en el texto objetivo — la menor cantidad de operaciones de inserción/eliminación.
Fuente: ABCABBA
Objetivo: CBABAC
Script de edición:
- A (eliminar A)
B (mantener B)
- C (eliminar C)
C (mantener C → insertar C al principio)
A (mantener A)
B (mantener B)
- B (eliminar B)
A (mantener A)
+ C (insertar C)
Diagrama del algoritmo
0 1 2 3 4 5 6 7
┌───┬───┬───┬───┬───┬───┬───┬───┐
0 │ → │ ↘ │ │ │ │ │ │ │ A
├───┼───┼───┼───┼───┼───┼───┼───┤
1 │ ↓ │ │ ↘ │ │ → │ ↘ │ │ │ B
├───┼───┼───┼───┼───┼───┼───┼───┤
2 │ │ ↓ │ │ ↘ │ ↓ │ │ ↘ │ │ C
├───┼───┼───┼───┼───┼───┼───┼───┤
3 │ │ │ ↓ │ │ │ ↓ │ │ ↘ │ A
├───┼───┼───┼───┼───┼───┼───┼───┤
4 │ │ │ │ ↓ │ ↘ │ │ ↓ │ │ B
├───┼───┼───┼───┼───┼───┼───┼───┤
5 │ │ │ │ │ ↓ │ │ │ ↓ │ B
├───┼───┼───┼───┼───┼───┼───┼───┤
6 │ │ │ │ │ │ ↓ │ ↘ │ │ A
└───┴───┴───┴───┴───┴───┴───┴───┘
C B A B A C B A
→ = derecha (eliminar) ↓ = abajo (insertar) ↘ = diagonal (mantener)
Complejidad temporal
| Caso |
Complejidad |
Descripción |
| Óptimo |
O(ND) |
N=longitud del texto, D=número de diferencias |
| Peor |
O((N+M)D) |
Textos completamente diferentes |
| Típico |
O(N+D²) |
Pocas diferencias |
II. Uso de la herramienta de comparación de texto de Tools Ku
Pasos de operación
- Abre la Herramienta de comparación de texto
- Pega el texto original en el lado izquierdo
- Pega el texto modificado en el lado derecho
- Consulta los resultados de las diferencias:
- Rojo: líneas eliminadas
- Verde: líneas añadidas
- Amarillo: líneas modificadas
- Soporta resaltado de diferencias por caracteres, preciso hasta cada cambio de carácter
Modos de comparación
| Modo |
Granularidad |
Caso de uso |
| Por líneas |
Comparación por línea |
Cambios de código |
| Por palabras |
Comparación por palabra |
Revisiones de documentos |
| Por caracteres |
Comparación por carácter |
Corrección precisa |
III. Técnicas especiales de JSON Diff
Uso de la herramienta JSON Diff
- Abre la Herramienta JSON Diff
- Pega dos datos JSON
- Consulta las diferencias estructuradas:
- Claves añadidas/eliminadas/modificadas
- Cambios de valores (incluyendo cambios de tipo)
- Rutas anidadas
JSON Diff vs Diff de texto
// Original
{ "name": "张三", "age": 25, "tags": ["a", "b"] }
// Modificado
{ "name": "张三", "age": 26, "tags": ["a", "c"], "email": "z@test.com" }
| Método de comparación |
Resultado |
| Diff de texto |
Muestra diferencias por línea, difícil de entender |
| JSON Diff |
age: 25→26, tags[1]: "b"→"c", +email: "z@test.com" |
--- a/config.json
+++ b/config.json
@@ -1,5 +1,6 @@
{
"name": "my-project",
- "version": "1.0.0",
+ "version": "2.0.0",
"description": "A tool",
- "private": true
+ "private": true,
+ "license": "MIT"
}
| Símbolo |
Significado |
--- |
Archivo original |
+++ |
Archivo modificado |
@@ -a,b +c,d @@ |
Posición de la diferencia (original desde línea a, b líneas; modificado desde línea c, d líneas) |
- |
Línea eliminada |
+ |
Línea añadida |
(espacio) |
Línea de contexto |
V. Generación y aplicación de parches
Generar parche
import * as Diff from 'diff';
const oldText = 'Hello World\nGoodbye World';
const newText = 'Hello World\nGoodbye Cruel World';
// Generar parche
const patch = Diff.createPatch('file.txt', oldText, newText, 'old', 'new');
console.log(patch);
Salida:
--- old/file.txt
+++ new/file.txt
@@ -1,2 +1,2 @@
Hello World
-Goodbye World
+Goodbye Cruel World
Aplicar parche
// Aplicar hacia adelante
const result = Diff.applyPatch(oldText, patch);
// Aplicar en reversa (revertir)
const reversed = Diff.applyPatch(newText, Diff.reversePatch(patch));
Fusión de tres vías
const base = 'Texto original';
const branchA = 'Modificación de A';
const branchB = 'Modificación de B';
const merged = Diff.mergeDiff3(base, branchA, branchB);
// Fusión automática, marca conflictos con <<<<<<< / ======= / >>>>>>>
VI. Escenarios de aplicación práctica
Detección de cambios en archivos de configuración
const oldConfig = JSON.parse(fs.readFileSync('config.old.json', 'utf-8'));
const newConfig = JSON.parse(fs.readFileSync('config.new.json', 'utf-8'));
const diff = Diff.diffJson(oldConfig, newConfig);
diff.forEach(part => {
if (part.added) console.log('Añadido:', JSON.stringify(part.value, null, 2));
if (part.removed) console.log('Eliminado:', JSON.stringify(part.value, null, 2));
});
Revisión de archivos de traducción
// Comparar la consistencia de claves de traducción chino-inglés
const zhKeys = Object.keys(flatten(zhTranslation));
const enKeys = Object.keys(flatten(enTranslation));
const missingInEn = zhKeys.filter(k => !enKeys.includes(k));
const missingInZh = enKeys.filter(k => !zhKeys.includes(k));
Pruebas de regresión de respuestas de API
const expected = await fs.readFile('fixtures/expected.json', 'utf-8');
const actual = await fetch('/api/data').then(r => r.json());
const diff = Diff.diffJson(JSON.parse(expected), actual);
if (diff.some(d => d.added || d.removed)) {
throw new Error('La respuesta de la API no coincide con la esperada');
}
VII. Optimización del rendimiento
| Estrategia de optimización |
Efecto |
Aplicable a |
| Limitar líneas de contexto |
Reduce el tamaño de salida |
Archivos grandes |
| Preprocesamiento con hash |
Omite líneas idénticas |
Archivos muy grandes |
| Comparación por bloques |
Procesamiento en paralelo |
Textos muy largos |
| Nivel de línea antes que nivel de carácter |
Reduce la carga de cálculo |
Archivos de código |
// Limitar líneas de contexto
const patch = Diff.createPatch('file', old, new, '', '', { context: 3 });