"difflib" --- Funciones auxiliares para calcular deltas
*******************************************************

**Código fuente:** Lib/difflib.py

======================================================================

This module provides classes and functions for comparing sequences. It
can be used for example, for comparing files, and can produce
information about file differences in various formats, including HTML
and context and unified diffs. For comparing directories and files,
see also, the "filecmp" module.

class difflib.SequenceMatcher

   Esta es una clase flexible para comparar pares de secuencias de
   cualquier tipo, siempre que los elementos de la secuencia sean
   *hashable*.  El algoritmo básico es anterior, y un poco mas
   agradable, que el publicado a fines de los 80' por Ratcliff y
   Obershelp, bajo el nombre hiperbólico de "*gestalt pattern
   matching*".  La idea es encontrar la subsecuencia coincidente
   contigua mas larga que no contenga elementos "no deseados"; estos
   elementos "no deseados" son aquellos que no son de interés por
   algún motivo, como ser lineas en blanco o espacios.  (El
   tratamiento de elementos no deseados es una extensión al algoritmo
   de Ratcliff y Obershelp). La misma idea se aplica recursivamente a
   las partes de la secuencia a la derecha e izquierda de cada
   subsecuencia correspondiente.  Esto no proporciona secuencias de
   edición mínimas, pero tiende a producir coincidencias que "parecen
   correctas" a las personas.

   **Complejidad temporal:** En el peor de los casos el algoritmo
   Ratcliff-Obershelp básico es de complejidad cúbica y de complejidad
   temporal cuadrática en el caso esperado. "SequenceMatcher" es de
   complejidad temporal cuadrática en el peor de los casos y el
   comportamiento del caso esperado depende de manera complicada de
   cuántos elementos tienen en común las secuencias; en el mejor de
   los casos la complejidad temporal es lineal.

   **Heurística automática de elementos no deseados:**
   "SequenceMatcher" implementa un método heurístico que identifica
   automáticamente a ciertos elementos como no deseados.   El método
   heurístico consiste en contar cuantas veces aparece cada elemento
   en la secuencia. Si las apariciones del duplicado de un elemento
   (después del primero) contabilizan mas del 1% de la secuencia, y a
   su vez la secuencia contiene mas de 200 elementos, este es
   identificado como "popular" y es considerado no deseado. Este
   método heurístico puede desactivarse estableciendo el argumento
   "autojunk" como "False" al crear la clase "SequenceMatcher".

   Nuevo en la versión 3.2: El parámetro *autojunk*.

class difflib.Differ

   Esta clase se utiliza para comparar secuencias de lineas de texto y
   producir diferencias o deltas en una forma legible por humanos.
   Differ usa "SequenceMatcher" tanto para comparar secuencias de
   lineas, como para comparar secuencias de caracteres entre lineas
   similares.

   Cada linea de un delta de "Differ" comienza con un código de dos
   letras:

   +------------+---------------------------------------------+
   | Código     | Significado                                 |
   |============|=============================================|
   | "'- '"     | línea única para la secuencia 1             |
   +------------+---------------------------------------------+
   | "'+ '"     | línea única para la secuencia 2             |
   +------------+---------------------------------------------+
   | "'  '"     | línea común a ambas secuencias              |
   +------------+---------------------------------------------+
   | "'? '"     | línea ausente en todas las secuencias de    |
   |            | entrada                                     |
   +------------+---------------------------------------------+

   Las líneas que empiezan con '"?"' intentan guiar al ojo hacia las
   diferencias intralíneas, y no estuvieron presentes en ninguna de
   las secuencias de entrada. Estas líneas pueden ser confusas si la
   secuencia contiene caracteres de tabulación.

class difflib.HtmlDiff

   Esta clase puede ser usada para crear una tabla HTML (o un archivo
   HTML completo que contenga la tabla) mostrando comparaciones lado a
   lado y linea por linea del texto, con cambios interlineales e
   intralineales. La tabla se puede generar en modo de diferencia
   completa o contextual.

   El constructor de esta clase es:

   __init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)

      Inicializa una instancia de "HtmlDiff".

      *tabsize* es un argumento por palabra clave opcional para
      especificar el espaciado de tabulación. Su valor predeterminado
      es "8".

      *wrapcolumn* es un argumento por palabra clave opcional para
      especificar el número de columnas donde las lineas serán
      divididas y ajustadas al ancho de columna, su valor por defecto
      es "None", donde las lineas no son ajustadas.

      *linejunk* y *charjunk* son argumentos por palabra clave
      opcionales que serán pasados a "ndiff()" (que es utilizado por
      "HtmlDiff" para generar las diferencias lado a lado en HTML).
      Refiérase a la documentación de "ndiff()" para conocer los
      detalles y valores por defecto de sus argumentos.

   Los siguientes métodos son públicos:

   make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')

      Compara *fromlines* y *tolines* (listas de cadenas de texto) y
      retorna una cadena de caracteres que representa un archivo HTML
      completo que contiene una tabla mostrando diferencias línea por
      línea del texto con cambios interlineales e intralineales
      resaltados.

      *fromdesc* y *todesc* son argumentos por palabra clave
      opcionales para especificar los encabezados de las columnas
      desde *fromdesc* hasta *todesc* en el archivo (ambas cadenas
      están vacías por defecto).

      *context* y *numlines* son parámetros opcionales. Establezca
      *context* como "True" para mostrar diferencias contextuales, de
      lo contrario su valor por defecto es "False" que muestra los
      archivos completos. El valor por defecto de *numlines* es "5".
      Cuando *context* es "True", *numlines* controla el número de
      lineas de contexto que rodean las diferencias resaltadas. Cuando
      *context* es "False", *numlines* controla el número de líneas
      que se muestran antes de una diferencia resaltada cuando se usan
      los hipervínculos "next" (un valor de cero produce que los
      hipervínculos "next" ubiquen el siguiente resaltado en la parte
      superior del navegador, sin ningún contexto principal).

      Nota:

        *fromdesc* y *todesc* se interpretan como HTML no escapado y
        se deben escapar correctamente si los datos son recibidos de
        fuentes no confiables.

      Distinto en la versión 3.5: Se agregó el argumento sólo de
      palabra clave *charset*.  La codificación de caracteres por
      defecto para documentos HTML se cambió de "'ISO-8859-1'" a
      "'utf-8'".

   make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)

      Compara *fromlines* y *tolines* (listas de cadenas de texto) y
      retorna una cadena de caracteres que representa una tabla HTML
      mostrando comparaciones lado a lado y línea por línea del texto
      con cambios interlineales e intralineales.

      Los argumentos para este método son los mismos que los del
      método "make_file()".

   "Tools/scripts/diff.py" es una herramienta de línea de comandos
   para esta clase y contiene un buen ejemplo sobre su uso.

difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

   Compara *a* y *b* (listas de cadenas de texto); retorna un delta
   (un *generator* que genera las lineas delta) en formato de
   diferencias de contexto.

   El formato de diferencias de contexto es una forma compacta de
   mostrar solamente las líneas que fueron modificadas y algunas
   líneas adicionales de contexto.  Los cambios son mostrados
   utilizando el estilo antes/después.  El número de líneas de
   contexto es determinado por *n*, cuyo valor por defecto es "3".

   Por defecto, las líneas de control (aquellas que comienzan con
   "***" o "---") son creadas con una línea nueva. Esto es de ayuda
   para que las entradas creadas por "io.IOBase.readlines()" generen
   diferencias que puedan ser utilizadas con "io.IOBase.writelines()"
   ya que ambas, la entrada y la salida, tienen líneas nuevas al
   final.

   Para entradas que no tienen nuevas líneas finales, establezca el
   argumento *lineterm* como """" de forma que la salida sea uniforme
   y libre de nuevas líneas.

   El formato de diferencias de contexto normalmente tiene un
   encabezado para nombres de archivos y tiempos de modificaciones.
   Alguno o todos estos debe ser especificado utilizando las cadenas
   de texto para *fromfile*, *tofile*, *fromfiledate* y *tofiledate*.
   Los tiempos de modificación son normalmente expresados en formato
   ISO 8601. Si no es especificado las cadenas por defecto son
   espacios en blanco.

   >>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
   >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
   >>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py'))
   *** before.py
   --- after.py
   ***************
   *** 1,4 ****
   ! bacon
   ! eggs
   ! ham
     guido
   --- 1,4 ----
   ! python
   ! eggy
   ! hamster
     guido

   Vea Una interfaz de línea de comandos para difflib para un ejemplo
   mas detallado.

difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)

   Retorna una lista de las mejores coincidencias "lo suficientemente
   buenas". *word* es una secuencia para la cual coincidencias
   cercanas son deseadas (usualmente una cadena de texto), y
   *possibilities* es una lista de secuencias contra la cual se
   compara *word* (comunmente una lista de cadenas de caracteres).

   Argumento opcional *n* (por defecto "3") es el máximo número de
   coincidencias cercanas a retornar; *n* debe ser mayor que "0".

   Argumento opcional *cutoff* (por defecto "0.6") es un flotante en
   el rango [0, 1]. Las posibilidades que no alcanzan un puntaje al
   menos similar al de *word* son ignoradas.

   Las mejores (no mas de *n*) coincidencias entre las posibilidades
   son retornadas en una lista, ordenadas por similitud de puntaje,
   las mas similares primero.

   >>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])
   ['apple', 'ape']
   >>> import keyword
   >>> get_close_matches('wheel', keyword.kwlist)
   ['while']
   >>> get_close_matches('pineapple', keyword.kwlist)
   []
   >>> get_close_matches('accept', keyword.kwlist)
   ['except']

difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)

   Compara *a* y *b* (listas de cadenas de texto); retorna un delta
   del estilo "Differ" (un *generator* que genera los deltas de las
   líneas).

   Parámetros de palabra clave opcional *linejunk* y *charjunk* son
   funciones de filtrado (o "None"):

   *linejunk*: Una función que acepta una sola cadena de caracteres
   como argumento, y retorna verdadero si la cadena de texto es un
   elemento no deseado, o falso si no lo es. Su valor por defecto es
   "None". Hay también una función a nivel del módulo
   "IS_LINE_JUNK()", que filtra líneas sin caracteres visibles,
   excepto como mucho un carácter de libra ("'#'") -- de cualquier
   forma la clase subyacente "SequenceMatcher" realiza un análisis
   dinámico sobre cuáles lineas son tan frecuentes como para
   constituir ruido, y esto usualmente funciona mejor que utilizando
   esta función.

   *charjunk*: Una función que acepta un carácter (una cadena de
   caracteres de longitud 1) como argumento, y retorna *True* si el
   carácter es un elemento no deseado, o *False* si no lo es. El valor
   por defecto es una función a nivel del módulo
   "IS_CHARACTER_JUNK()", que filtra caracteres de espacios en blanco
   (un espacio en blanco o tabulación; es una mala idea incluir saltos
   de lineas en esto!)

   "Tools/scripts/ndiff.py" es una interfaz de línea de comandos para
   esta función.

   >>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
   ...              'ore\ntree\nemu\n'.splitlines(keepends=True))
   >>> print(''.join(diff), end="")
   - one
   ?  ^
   + ore
   ?  ^
   - two
   - three
   ?  -
   + tree
   + emu

difflib.restore(sequence, which)

   Retorna uno de las dos secuencias que generaron un delta.

   Dada una *sequence* producida por "Differ.compare()" o "ndiff()",
   extrae las líneas originadas por el archivo 1 o 2 (parámetro
   *which*), quitando los prefijos de la línea.

   Ejemplo:

   >>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
   ...              'ore\ntree\nemu\n'.splitlines(keepends=True))
   >>> diff = list(diff) # materialize the generated delta into a list
   >>> print(''.join(restore(diff, 1)), end="")
   one
   two
   three
   >>> print(''.join(restore(diff, 2)), end="")
   ore
   tree
   emu

difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

   Compara *a* y *b* (listas de cadenas de caracteres); retorna un
   delta (un *generator* que genera los delta de líneas) en formato de
   diferencias unificado.

   Las diferencias unificadas son una forma compacta de mostrar sólo
   las líneas que presentan cambios y algunas líneas de contexto
   adicionales. Los cambios son mostrados en una sola línea (en lugar
   de bloques separados antes y después). El número de líneas de
   contexto se establece mediante *n*, cuyo valor por defecto es tres.

   Por defecto, las líneas de control de diferencias (aquellas con
   "---", "+++", o "@@") son creadas con un salto de línea nuevo. Esto
   es de ayuda para que las entradas creadas por
   "io.IOBase.readlines()" generen diferencias que puedan ser
   utilizadas con "io.IOBase.writelines()" ya que ambas, la entrada y
   la salida, tienen líneas nuevas al final.

   Para entradas que no tienen nuevas líneas finales, establezca el
   argumento *lineterm* como """" de forma que la salida sea uniforme
   y libre de nuevas líneas.

   El formato de diferencias de contexto normalmente tiene un
   encabezado para nombres de archivos y tiempos de modificaciones.
   Alguno o todos estos debe ser especificado utilizando las cadenas
   de texto para *fromfile*, *tofile*, *fromfiledate* y *tofiledate*.
   Los tiempos de modificación son normalmente expresados en formato
   ISO 8601. Si no es especificado las cadenas por defecto son
   espacios en blanco.

   >>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
   >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
   >>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py'))
   --- before.py
   +++ after.py
   @@ -1,4 +1,4 @@
   -bacon
   -eggs
   -ham
   +python
   +eggy
   +hamster
    guido

   Vea Una interfaz de línea de comandos para difflib para un ejemplo
   mas detallado.

difflib.diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')

   Compara *a* y *b* (listas de objetos de bytes) usando *dfunc*;
   produce una secuencia de líneas delta (también bytes) en el formato
   retornado por *dfunc*. *dfunc* debe ser invocable, comúnmente
   cualquiera de "unified_diff()" o "context_diff()".

   Permite comparar datos con codificación desconocida o
   inconsistente. Todas las entradas, excepto *n*, deben ser objetos
   de bytes, no cadenas de texto. Funciona convirtiendo sin pérdidas
   todas las entradas (excepto *n*) a cadenas de texto, e invoca
   "dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n,
   lineterm)". La salida de *dfunc* es entonces convertida nuevamente
   a bytes, de forma que las líneas delta que son recibidas tienen la
   misma codificación desconocida/inconsistente que *a* y *b*.

   Nuevo en la versión 3.5.

difflib.IS_LINE_JUNK(line)

   Retorna "True" para líneas que deben ser ignoradas. La línea *line*
   es ignorada si *line* es un espacio vacío o contiene un solo "'#'",
   en cualquier otro caso no es ignorado. Es usado como valor por
   defecto para el parámetro *linejunk* por "ndiff()" en versiones
   anteriores.

difflib.IS_CHARACTER_JUNK(ch)

   Retorna "True" para los caracteres que deben ser ignorados. El
   carácter *ch* es ignorado si *ch* es un espacio en blanco o
   tabulación, en cualquier otro caso no es ignorado. Es utilizado
   como valor por defecto para el parámetro *charjunk* en "ndiff()".

Ver también:

  Pattern Matching: The Gestalt Approach
     Discusión de un algoritmo similar por John W. Ratcliff y D. E.
     Metzener. Esto fue publicado en Dr. Dobb's Journal en Julio de
     1988.


Objetos *SequenceMatcher*
=========================

La clase "SequenceMatcher" tiene este constructor:

class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

   El argumento opcional *isjunk* debe ser "None" (que es su valor por
   defecto) o una función de un solo argumento que reciba un elemento
   de la secuencia y retorne verdadero si y solo si el elemento es no
   deseado y deba ser ignorado. Pasar el argumento *isjunk* como
   "None" es equivalente a pasar "lambda x: False"; en otras palabras,
   ningún elemento es ignorado. Por ejemplo, pasar:

      lambda x: x in " \t"

   si se están comparando líneas como secuencias de caracteres, y no
   se quiere sincronizar en espacios blancos o tabulaciones.

   Los argumentos opcionales *a* y *b* son las secuencias a comparar;
   ambos tienen como valor por defecto una cadena de texto vacía. Los
   elementos de ambas secuencias deben ser *hashable*.

   El argumento opcional *autojunk* puede ser usado para deshabilitar
   la heurística automática de elementos no deseados.

   Nuevo en la versión 3.2: El parámetro *autojunk*.

   Los objetos *SequenceMatcher* reciben tres atributos: *bjunk* es el
   conjunto de elementos de *b* para los cuales *isjunk* es "True";
   *bpopular* es el set de elementos que no son basura considerados
   populares por la heurística (si no es que fue deshabilitada); *b2j*
   es un diccionario que mapea elementos de *b* a una lista de
   posiciones donde estos ocurren. Los tres atributos son reseteados
   cuando *b* es reseteado mediante "set_seqs()" o "set_seq2()".

   Nuevo en la versión 3.2: Los atributos *bjunk* y *bpopular*.

   Los objetos "SequenceMatcher" tienen los siguientes métodos:

   set_seqs(a, b)

      Establece las dos secuencias a ser comparadas.

   "SequenceMatcher" calcula y almacena información detallada sobre la
   segunda secuencia, con lo cual si quieres comparar una secuencia
   contra muchas otras, usa "set_seq2()" para establecer la secuencia
   común una sola vez y llamar "set_seq1()" repetidamente, una vez por
   cada una de las otras secuencias.

   set_seq1(a)

      Establece la primer secuencia a ser comparada. La segunda
      secuencia a ser comparada no es modificada.

   set_seq2(b)

      Establece la segunda secuencia a ser comparada. La primera
      secuencia a ser comparada no es modificada.

   find_longest_match(alo, ahi, blo, bhi)

      Encuentra el bloque de coincidencia mas largo en "a[alo:ahi]" y
      "b[blo:bhi]".

      Si *isjunk* fue omitido o es "None", "find_longest_match()"
      retorna "(i, j, k)" tal que "a[i:i+k]" es igual a "b[j:j+k]",
      donde "alo <= i <= i+k <= ahi" y "blo <= j <= j+k <= bhi". Para
      todo "(i', j', k')" cumpliendo esas condiciones, las condiciones
      adicionales "k >= k'", "i <= i'", y si "i == i'", "j <= j'"
      también se cumplen. En otras palabras, de todos los bloques
      coincidentes máximos, retorna aquel que comienza antes en *a*, y
      de todos esos bloques coincidentes máximos que comienzan antes
      en *a*, retorna aquel que comienza antes en *b*.

      >>> s = SequenceMatcher(None, " abcd", "abcd abcd")
      >>> s.find_longest_match(0, 5, 0, 9)
      Match(a=0, b=4, size=5)

      Si se porporcionó *isjunk*, primero se determina el bloque
      coincidente mas largo como fue indicado anteriormente, pero con
      la restricción adicional de que no aparezca ningún elemento no
      deseado en el bloque. Entonces, ese bloque se extiende tan lejos
      como sea posible haciendo coincidir (solo) elementos no deseados
      de ambos lados. Por lo tanto, el bloque resultante nunca hará
      coincidir ningún elemento no deseado, excepto que un elemento no
      deseado idéntico pase a ser adyacente a una coincidencia
      interesante.

      Este es el mismo ejemplo que el mostrado anteriormente, pero
      considerando elementos en blanco como no deseados. Esto previene
      que "' abcd'" sea coincidente con "'abcd'" en el final de la
      segunda secuencia directamente. En cambio, sólo el "'abcd'"
      puede coincidir, y coincide con el "'abcd'" que se encuentre mas
      a la izquierda en la segunda secuencia:

      >>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
      >>> s.find_longest_match(0, 5, 0, 9)
      Match(a=1, b=0, size=4)

      Si no coincide ningún bloque, esto retorna "(alo, blo, 0)".

      Este método retorna un *named tuple* "Match(a, b, size)".

   get_matching_blocks()

      Retorna una lista de triplas (tuplas de tres elementos)
      describiendo subsecuencias coincidentes no superpuestas. Cada
      tripla sigue el formato "(i, j, n)", y significa que "a[i:i+n]
      == b[j:j+n]". Las triplas son monótonamente crecientes en *i* y
      *j*.

      La última tripla es un objeto ficticio (dummy), y tiene el valor
      "(len(a), len(b), 0)". Es la única tripla con "n == 0".  Si "(i,
      j, n)" y "(i', j', n')" son triplas adyacentes en la lista, y la
      segunda no es el último elemento de la lista, entonces "i+n <
      i'" o "j+n < j'"; en otras palabras, las triplas adyacentes
      describen bloques iguales no adyacentes.

         >>> s = SequenceMatcher(None, "abxcd", "abcd")
         >>> s.get_matching_blocks()
         [Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]

   get_opcodes()

      Retorna una lista de quíntuplas (tuplas de cinco elementos)
      describiendo como convertir *a* en *b*. Cada tupla tiene la
      forma "(tag, i1, i2, j1, j2)". En la primer tupla se cumple que
      "i1 == j1 == 0", y las tuplas restantes tienen *i1* igual al
      *i2* de la tupla precedente, y de igual manera, *j1* igual al
      *j2* de la tupla anterior.

      Los valores de *tag* son cadenas de caracteres, con el siguiente
      significado:

      +-----------------+-----------------------------------------------+
      | Valor           | Significado                                   |
      |=================|===============================================|
      | "'replace'"     | "a[i1:i2]" debe ser reemplazado por           |
      |                 | "b[j1:j2]".                                   |
      +-----------------+-----------------------------------------------+
      | "'delete'"      | "a[i1:i2]" debe ser eliminado.  Nótese que en |
      |                 | este caso "j1 == j2".                         |
      +-----------------+-----------------------------------------------+
      | "'insert'"      | "b[j1:j2]" debe ser insertado en "a[i1:i1]".  |
      |                 | Nótese que en este caso "i1 == i2".           |
      +-----------------+-----------------------------------------------+
      | "'equal'"       | "a[i1:i2] == b[j1:j2]" (las subsecuencias son |
      |                 | iguales).                                     |
      +-----------------+-----------------------------------------------+

      Por ejemplo:

         >>> a = "qabxcd"
         >>> b = "abycdf"
         >>> s = SequenceMatcher(None, a, b)
         >>> for tag, i1, i2, j1, j2 in s.get_opcodes():
         ...     print('{:7}   a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
         ...         tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
         delete    a[0:1] --> b[0:0]      'q' --> ''
         equal     a[1:3] --> b[0:2]     'ab' --> 'ab'
         replace   a[3:4] --> b[2:3]      'x' --> 'y'
         equal     a[4:6] --> b[3:5]     'cd' --> 'cd'
         insert    a[6:6] --> b[5:6]       '' --> 'f'

   get_grouped_opcodes(n=3)

      Retorna un *generator* de grupos de hasta *n* líneas de
      contexto.

      Empezando con los grupos retornados por "get_opcodes()", este
      método separa grupos con cambios menores y elimina los rangos
      intermedios que no tienen cambios.

      Los grupos son retornados en el mismo formato que
      "get_opcodes()".

   ratio()

      Retorna una medida de la similitud de las secuencias como un
      flotante en el rango [0, 1].

      Donde T es el número total de elementos en ambas secuencias y M
      es el número de coincidencias, esto es 2.0*M / T. Nótese que
      esto es "1.0" si las secuencias son idénticas y "0.0" si no
      tienen nada en común.

      Esto es computacionalmente costoso si "get_matching_blocks()" o
      "get_opcodes()" no fueron ejecutados, in tal caso deberías
      considerar primero "quick_ratio()" o "real_quick_ratio()" para
      obtener un límite superior.

      Nota:

        Precaución: El resultado de una llamada a "ratio()" puede
        depender del orden de los argumentos. Por ejemplo:

           >>> SequenceMatcher(None, 'tide', 'diet').ratio()
           0.25
           >>> SequenceMatcher(None, 'diet', 'tide').ratio()
           0.5

   quick_ratio()

      Retorna un límite superior en "ratio()" relativamente rápido.

   real_quick_ratio()

      Retorna un límite superior en "ratio()" muy rápido.

Los tres métodos que retornan la proporción de coincidencias con el
total de caracteres pueden dar diferentes resultados debido a los
distintos niveles de aproximación, a pesar de que "quick_ratio()" y
"real_quick_ratio()" son siempre al menos tan grandes como "ratio()":

>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0


"SequenceMatcher" Ejemplos
==========================

Este ejemplo compara dos cadenas de texto, considerando los espacios
en blanco como caracteres no deseados:

>>> s = SequenceMatcher(lambda x: x == " ",
...                     "private Thread currentThread;",
...                     "private volatile Thread currentThread;")

"ratio()" retorna un flotante en el rango [0, 1], cuantificando la
similitud entre las secuencias. Siguiendo la regla del pulgar, un
"ratio()" por encima de 0.6 significa que las secuencias son
coincidencias cercanas:

>>> print(round(s.ratio(), 3))
0.866

Si solamente estás interesado en cuándo las secuencias coinciden,
"get_matching_blocks()" es útil:

>>> for block in s.get_matching_blocks():
...     print("a[%d] and b[%d] match for %d elements" % block)
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements

Nótese que la última tupla retornada por "get_matching_blocks()" es
siempre un objeto ficticio (dummy), "(len(a), len(b), 0)", y este es
el único caso en el cual el último elemento de la tupla (el número de
elementos coincidentes) es "0".

Si quieres saber como cambiar la primer secuencia con la segunda, usa
"get_opcodes()":

>>> for opcode in s.get_opcodes():
...     print("%6s a[%d:%d] b[%d:%d]" % opcode)
 equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
 equal a[8:29] b[17:38]

Ver también:

  * La función "get_close_matches()" en este módulo que muestra lo
    simple que es el código que construye "SequenceMatcher" puede ser
    utilizada para hacer un trabajo útil.

  * Una receta simple de un controlador de versiones para una
    aplicación pequeña construida con "SequenceMatcher".


Objetos *Differ*
================

Nótese que los deltas generados por "Differ" no dicen ser diferencias
**mínimas**. Todo lo contrario, las diferencias mínimas suelen ser
contra-intuitivas, ya que se sincronizan en cualquier lugar posible, a
veces coinciden accidentalmente con 100 páginas de diferencia.
Restringiendo los puntos de sincronización a coincidencias contiguas
se preserva cierta noción de cercanía, con el costo adicional de
producir diferencias mas largas.

La clase "Differ" tiene el siguiente constructor:

class difflib.Differ(linejunk=None, charjunk=None)

   Parámetros de palabra clave opcionales *linejunk* y *charjunk* son
   para funciones de filtrado (o "None"):

   *linejunk*: Una función que acepta una sola cadena de texto como
   argumento y retorna verdadero si la cadena de texto es un elemento
   no deseado. Su valor por defecto es "None", lo que significa que
   ninguna línea es considerada no deseada.

   *charjunk*: Una función que acepta un solo carácter como argumento
   (una cadena de caracteres de longitud 1) y retorna verdadero si el
   carácter es un elemento no deseado. Su valor por defecto es "None",
   lo que significa que ningún carácter es considerado no deseado.

   Estas funciones de elementos no deseados aceleran la coincidencia
   para encontrar diferencies y no hacen que se ignoren líneas o
   caracteres diferentes. Lea la descripción del parámetro *isjunk* en
   el método "find_longest_match()" para una explicación mas
   detallada.

   Los objetos "Differ" son usados (una vez generados los deltas)
   mediante un solo método:

   compare(a, b)

      Compara dos secuencias de líneas y genera el delta
      correspondiente (una secuencia de líneas).

      Cada secuencia debe contener cadenas de texto individuales de
      una sola linea terminadas con una línea nueva. Este tipo de
      secuencias pueden ser obtenidas mediante el método "readlines()"
      de objetos de tipo archivo. Los delta generados consisten
      también en cadenas de texto terminadas en nuevas lineas, listas
      para imprimirse tal cual a través del método "writelines()" de
      un objeto de tipo archivo.


Ejemplo de *Differ*
===================

Este ejemplo compara dos textos. Primero preparamos los textos,
secuencias de cadenas de texto individuales de una sola línea
terminadas con una línea nueva (este tipo de secuencias también pueden
ser obtenidas mediante el método "readlines()" de objetos de tipo
archivo):

>>> text1 = '''  1. Beautiful is better than ugly.
...   2. Explicit is better than implicit.
...   3. Simple is better than complex.
...   4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len(text1)
4
>>> text1[0][-1]
'\n'
>>> text2 = '''  1. Beautiful is better than ugly.
...   3.   Simple is better than complex.
...   4. Complicated is better than complex.
...   5. Flat is better than nested.
... '''.splitlines(keepends=True)

Luego instanciamos el objeto *Differ*:

>>> d = Differ()

Nótese que cuando instanciamos un objeto "Differ" deberíamos pasar
funciones para filtrar lineas y caracteres no deseados. Consulte el
constructor de "Differ()" para mas detalles.

Finalmente, comparamos las dos:

>>> result = list(d.compare(text1, text2))

"result" es una lista de cadenas de caracteres, entonces vamos a
mostrarlo de una forma elegante:

>>> from pprint import pprint
>>> pprint(result)
['    1. Beautiful is better than ugly.\n',
 '-   2. Explicit is better than implicit.\n',
 '-   3. Simple is better than complex.\n',
 '+   3.   Simple is better than complex.\n',
 '?     ++\n',
 '-   4. Complex is better than complicated.\n',
 '?            ^                     ---- ^\n',
 '+   4. Complicated is better than complex.\n',
 '?           ++++ ^                      ^\n',
 '+   5. Flat is better than nested.\n']

Representado como una sola cadena de caracteres de múltiples líneas se
ve así:

>>> import sys
>>> sys.stdout.writelines(result)
    1. Beautiful is better than ugly.
-   2. Explicit is better than implicit.
-   3. Simple is better than complex.
+   3.   Simple is better than complex.
?     ++
-   4. Complex is better than complicated.
?            ^                     ---- ^
+   4. Complicated is better than complex.
?           ++++ ^                      ^
+   5. Flat is better than nested.


Una interfaz de línea de comandos para "difflib"
================================================

Este ejemplo muestra como usar difflib para crear una herramienta de
diferencias. También puedes encontrarla en la distribución estándar de
Python como "Tools/scripts/diff.py".

   #!/usr/bin/env python3
   """ Command line interface to difflib.py providing diffs in four formats:

   * ndiff:    lists every line and highlights interline changes.
   * context:  highlights clusters of changes in a before/after format.
   * unified:  highlights clusters of changes in an inline format.
   * html:     generates side by side comparison with change highlights.

   """

   import sys, os, difflib, argparse
   from datetime import datetime, timezone

   def file_mtime(path):
       t = datetime.fromtimestamp(os.stat(path).st_mtime,
                                  timezone.utc)
       return t.astimezone().isoformat()

   def main():

       parser = argparse.ArgumentParser()
       parser.add_argument('-c', action='store_true', default=False,
                           help='Produce a context format diff (default)')
       parser.add_argument('-u', action='store_true', default=False,
                           help='Produce a unified format diff')
       parser.add_argument('-m', action='store_true', default=False,
                           help='Produce HTML side by side diff '
                                '(can use -c and -l in conjunction)')
       parser.add_argument('-n', action='store_true', default=False,
                           help='Produce a ndiff format diff')
       parser.add_argument('-l', '--lines', type=int, default=3,
                           help='Set number of context lines (default 3)')
       parser.add_argument('fromfile')
       parser.add_argument('tofile')
       options = parser.parse_args()

       n = options.lines
       fromfile = options.fromfile
       tofile = options.tofile

       fromdate = file_mtime(fromfile)
       todate = file_mtime(tofile)
       with open(fromfile) as ff:
           fromlines = ff.readlines()
       with open(tofile) as tf:
           tolines = tf.readlines()

       if options.u:
           diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
       elif options.n:
           diff = difflib.ndiff(fromlines, tolines)
       elif options.m:
           diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
       else:
           diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)

       sys.stdout.writelines(diff)

   if __name__ == '__main__':
       main()
