Python: comparar dos archivos y exportarlo

publicado por: Anonymous

Buscando por la web he conseguido mostrar las diferencias entre dos archivos de texto en uno nuevo, con el siguiente código:

with open('file1.txt', 'r') as file1:
    with open('file2.txt', 'r') as file2:
        difference = set(file1).difference(file2)

with open('diff.txt', 'w') as file_out:
    for line in difference:
        file_out.write(line)

El problema está en que me exporta las diferencias con líneas ordenadas alfabéticamente. Por ej:

file1.txt:

hola
casa
televisor
sillón
bebida

file2.txt:

hola
casa

diff.txt

bebida
sillón
televisor

¿Como hago para que en diff.txt no queden las lineas ordenadas alfabéticamente?

solución

El problema es que los conjuntos no mantienen el orden entre sus elementos, de hecho la salida no es en orden alfabético, no tiene orden directamente. Si ejecutas el script repetidas veces obtendrás diferentes salidas. Cualquier supuesto orden en conjuntos o diccionarios (como ocurre en Python 3.6) debe considerarse hasta el momento solo un efecto colateral de la implementación.

La pena es que la intersección de conjuntos es un método muy eficiente para encontrar las diferencias entre dos conjuntos de datos, dado que las búsquedas en tablas hash tienen complejidad O(1) de media, O(n) en el peor de los casos.

Dado que buscas las filas de file1.txt que no están en file2.txt pero no a la inversa, puedes seguir usando un conjunto para almacenar las líneas de file2.txt e iterar en orden sobre file1.txt comprobando si la línea existe o no en el conjunto de file2.txt mediante in:

with open('file1.txt', 'r') as file1:
    with open('file2.txt', 'r') as file2:
        with open ("output.txt", "w") as out_file:
            f2_lines = set(file2)
            for line in file1:
                if line not in f2_lines:
                    out_file.write(line)

La salida para tu ejemplo sería siempre:

televisor
sillón
bebida

Ahora bien, si existe una línea repetida en file1 esta aparecerá también repetida las mismas veces en el fichero de salida. Si no quieres esto puedes añadir la línea al conjunto cuando es encontrada la primera vez:

with open('file1.txt', 'r') as file1:
    with open('file2.txt', 'r') as file2:
        with open ("output.txt", "w") as out_file:
            f2_lines = set(file2)
            for line in file1:
                if line not in f2_lines:
                    out_file.write(line)
                    f2_lines.add(line)

Para el siguiente contenido de file1.txt:

hola
casa
televisor
bebida
sillón
bebida

El primer código nos retornará:

televisor
bebida
sillón
bebida

y este último:

televisor
bebida
sillón


ADVERTENCIA: tanto el código original de la pregunta como el anterior de esta respuesta comparan las filas crudas, es decir incluyendo el carácter de nueva línea. Esto es importante, porque si la última línea de los ficheros no acabara con carácter de nueva línea o los ficheros usaran distinto carácter de nueva línea (CR+LF. LF, CR, …) la comparación fallaría. En estos casos, se puede recurrir a aplicar str.rstrip sobre cada fila o corregir el problema en los ficheros previamente a la comparación, etc.

Si tus archivos tienen exactamente el mismo contendido hasta el final de fichero2.txt y solo varían en que file1.txt tiene lineas nuevas que no fueron añadidas a file2.txt tal y como tu ejemplo muestra, entonces podemos jugar con el cursor en vez de cargar datos en memoria:

import shutil

with open('file1.txt', 'r') as file1:
    with open('file2.txt', 'r') as file2:
        with open ("output.txt", "w") as out_file:
            file2.seek(0, 2)                      # cursor de file2 al final del fichero  
            file1.seek(file2.tell())              # Cursor de file1 en la posición del de file2
            shutil.copyfileobj(file1, out_file)   # Copiamos file1 hasta el final

La salida esperada es la misma que la del primer código, no omite posibles duplicados. Si file1.txt es igual o menor en tamaño que file2.txt el fichero de salida quedará vacío.

Respondido por: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *