Algoritmo de diferencia? [cerrado]

164

He estado buscando una locura por una explicación de un algoritmo diff que funciona y es eficiente.

Lo más cercano que obtuve es este enlace a RFC 3284 (de varias publicaciones de blog de Eric Sink), que describe en términos perfectamente comprensibles el formato de datos en el que se almacenan los resultados de diferencias. Sin embargo, no se menciona en absoluto cómo un programa alcanzaría estos resultados al hacer una diferencia.

Estoy tratando de investigar esto por curiosidad personal, porque estoy seguro de que debe haber compensaciones al implementar un algoritmo diff, que a veces son bastante claras cuando miras diffs y te preguntas "¿por qué el programa diff eligió esto como un cambio? ¿en lugar de eso?"...

¿Dónde puedo encontrar una descripción de un algoritmo eficiente que termine generando VCDIFF?
Por cierto, si encuentra una descripción del algoritmo real utilizado por DiffMerge de SourceGear, sería aún mejor.

NOTA: la subsecuencia común más larga no parece ser el algoritmo utilizado por VCDIFF, parece que están haciendo algo más inteligente, dado el formato de datos que usan.

Daniel Magliola
fuente
¿Nada en wikipedia? Quizás pueda intentar encontrar otra implementación en un idioma de alto nivel como python, que podría ser más fácil de entender que una implementación en C. Python es famoso por ser fácilmente legible? Hay un difflib en python. Aquí está la url de la fuente. La fuente tiene muchos comentarios sobre algoritmos diff. svn.python.org/view/python/trunk/Lib/…
bsergean
44
Los RFC no están destinados a describir algoritmos. Están destinados a describir interfaces (/ protocolos).
2
En realidad, el núcleo del algoritmo diff, el problema de subsecuencia común más largo, se puede encontrar en Wikipedia. Esta página ofrece una descripción general del algoritmo y el código de muestra que encontré útil cuando necesitaba escribir un diff personalizado: en.wikipedia.org/wiki/Longest_common_subsequence_problem
Corwin Joy
3
Quizás esto ayude: paulbutler.org/archives/a-simple-diff-algorithm-in-php Seguro que es increíble, y es muy pequeño (solo 29 líneas en total ; tiene 2 funciones). Es similar a la cosa de comparación de revisión de edición de Stack Overflow.
Nathan
VCDIFF no es para diferencias legibles por humanos. Emplea instrucciones de agregar, copiar y ejecutar en lugar de las instrucciones de eliminación e inserción más legibles por humanos emitidas por la mayoría de los algoritmos de diferencias de texto sin formato. Para VCDIFF necesita algo como el algoritmo xdelta descrito aquí xmailserver.org/xdfs.pdf
asgerhallas

Respuestas:

175

Un algoritmo de diferencia O (ND) y sus variaciones es un documento fantástico y es posible que desee comenzar allí. Incluye pseudocódigo y una buena visualización de los recorridos del gráfico involucrados en hacer la diferencia.

Sección 4 del documento presenta algunas mejoras en el algoritmo que lo hacen muy efectivo.

La implementación exitosa de esto lo dejará con una herramienta muy útil en su caja de herramientas (y probablemente también una excelente experiencia).

Generar el formato de salida que necesita a veces puede ser complicado, pero si comprende los aspectos internos del algoritmo, entonces debería poder generar todo lo que necesita. También puede introducir heurísticas para afectar la salida y hacer ciertas compensaciones.

Aquí hay una página que incluye un poco de documentación, código fuente completo y ejemplos de un algoritmo de diferencias usando las técnicas en el algoritmo mencionado anteriormente.

El código fuente parece seguir de cerca el algoritmo básico y es fácil de leer.

También hay un poco en la preparación de la entrada, que puede ser útil. Hay una gran diferencia en la salida cuando difiere por carácter o token (palabra).

¡Buena suerte!

jscharf
fuente
1
En caso de que el enlace salga mal, este es Myers 1986; ver, por ejemplo, citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 - además incluye un enlace al diffdocumento Unix de Hunt y McIlroy.
tripleee
34

Comenzaría mirando el código fuente real de diff, que GNU pone a disposición .

Para comprender cómo funciona realmente ese código fuente, los documentos en ese paquete hacen referencia a los documentos que lo inspiraron:

El algoritmo básico se describe en "Un algoritmo de diferencia O (ND) y sus variaciones", Eugene W. Myers, 'Algorithmica' Vol. 1 No. 2, 1986, págs. 251-266; y en "Un programa de comparación de archivos", Webb Miller y Eugene W. Myers, "Software - Practice and Experience" vol. 15 No. 11, 1985, págs. 1025-1040. El algoritmo se descubrió de forma independiente como se describe en "Algoritmos para la coincidencia aproximada de cadenas", E. Ukkonen, "Información y control" vol. 64, 1985, págs. 100-118.

Leer los documentos y luego mirar el código fuente para una implementación debería ser más que suficiente para entender cómo funciona.

paxdiablo
fuente
80
En resumen, a veces descubrir el algoritmo subyacente a partir del código fuente real (especialmente si está optimizado para ser eficiente) puede ser bastante complejo. Podré entender lo que hace el programa paso a paso, pero no exactamente "por qué", o una descripción general de alto nivel sobre eso ... Ejemplo: Nunca entenderías cómo funcionan las expresiones regulares (o qué son) por mirando la implementación de las expresiones regulares de Perl. O si pudieras hacer eso, entonces me inclino, definitivamente necesito una descripción general más explicada y de mayor nivel para descubrir qué está pasando.
Daniel Magliola el
3
Nunca entiendo cómo funciona la gran mayoría de Perl :-), pero hay un enlace en los documentos del paquete (ver actualización) que lo llevará a la literatura que lo describe (es el algoritmo diff, no Perl).
paxdiablo
32
No leas el código. Lee el papel.
Ira Baxter
31

Ver https://github.com/google/diff-match-patch

"Las bibliotecas Diff Match y Patch ofrecen algoritmos robustos para realizar las operaciones necesarias para sincronizar texto sin formato ... Actualmente disponible en Java, JavaScript, C ++, C # y Python"

Consulte también la página de diferencias de wikipedia.org y - " Bram Cohen: el problema de diferencias se ha resuelto "

Matthew Hannigan
fuente
2
Solo quería mencionar que el algoritmo de Cohen también parece ser conocido como Patience Diff. Es el algoritmo de diferencia (¿predeterminado?) En bazar y uno opcional en git.
Dale Hagglund
13

Vine aquí buscando el algoritmo diff y luego hice mi propia implementación. Lo siento, no sé sobre vcdiff.

Wikipedia : a partir de una subsecuencia común más larga, solo es un pequeño paso para obtener una salida de tipo diff: si un elemento está ausente en la subsecuencia pero está presente en el original, debe haberse eliminado. (Las marcas '-', a continuación.) Si está ausente en la subsecuencia pero está presente en la segunda secuencia, debe haberse agregado. (Las marcas '+').

Buena animación del algoritmo LCS aquí .

Enlace a una implementación rápida de LCS ruby ​​aquí .

Mi lenta y simple adaptación rubí está abajo.

def lcs(xs, ys)
  if xs.count > 0 and ys.count > 0
    xe, *xb = xs
    ye, *yb = ys
    if xe == ye
      return [xe] + lcs(xb, yb)
    end
    a = lcs(xs, yb)
    b = lcs(xb, ys)
    return (a.length > b.length) ? a : b
  end
  return []
end

def find_diffs(original, modified, subsequence)
  result = []
  while subsequence.length > 0
    sfirst, *subsequence = subsequence
    while modified.length > 0
      mfirst, *modified = modified
      break if mfirst == sfirst
      result << "+#{mfirst}"
    end
    while original.length > 0
      ofirst, *original = original
      break if ofirst == sfirst
      result << "-#{ofirst}"
    end
    result << "#{sfirst}"
  end
  while modified.length > 0
    mfirst, *modified = modified
    result << "+#{mfirst}"
  end
  while original.length > 0
    ofirst, *original = original
    result << "-#{ofirst}"
  end
  return result
end

def pretty_diff(original, modified)
  subsequence = lcs(modified, original)
  diffs = find_diffs(original, modified, subsequence)

  puts 'ORIG      [' + original.join(', ') + ']'
  puts 'MODIFIED  [' + modified.join(', ') + ']'
  puts 'LCS       [' + subsequence.join(', ') + ']'
  puts 'DIFFS     [' + diffs.join(', ') + ']'
end

pretty_diff("human".scan(/./), "chimpanzee".scan(/./))
# ORIG      [h, u, m, a, n]
# MODIFIED  [c, h, i, m, p, a, n, z, e, e]
# LCS       [h, m, a, n]
# DIFFS     [+c, h, +i, -u, m, +p, a, n, +z, +e, +e]
neoneye
fuente