Python, recorre las líneas de un archivo; si la línea es igual a la línea en otro archivo, devuelve la línea original

El archivo de texto 1 tiene el siguiente formato:

'WORD': 1
'MULTIPLE WORDS': 1
'WORD': 2

etc.

Es decir, una palabra separada por dos puntos seguida de un número.

El archivo de texto 2 tiene el siguiente formato:

'WORD'
'WORD'

etc.

Necesito extraer palabras individuales (es decir, solo PALABRA, no PALABRAS MÚLTIPLES) del Archivo 1 y, si coinciden con una palabra del Archivo 2, devolver la palabra del Archivo 1 junto con su valor.

Tengo un código que funciona mal:

def GetCounts(file1, file2):
    target_contents  = open(file1).readlines()  #file 1 as list--> 'WORD': n
    match_me_contents = open(file2).readlines()   #file 2 as list -> 'WORD'
    ls_stripped = [x.strip('\n') for x in match_me_contents]  #get rid of newlines

    match_me_as_regex= re.compile("|".join(ls_stripped))   

    for line in target_contents:
        first_column = line.split(':')[0]  #get the first item in line.split
        number = line.split(':')[1]   #get the number associated with the word
        if len(first_column.split()) == 1: #get single word, no multiple words 
            """ Does the word from target contents match the word
            from match_me contents?  If so, return the line from  
            target_contents"""
            if re.findall(match_me_as_regex, first_column):  
                print first_column, number

#OUTPUT: WORD, n
         WORD, n
         etc.

Debido al uso de expresiones regulares, la salida es irregular. El código devolverá 'asset, 2', por ejemplo, ya que re.findall () coincidirá con 'set' de match_me. Necesito hacer coincidir target_word con la palabra completa de match_me para bloquear la salida incorrecta resultante de coincidencias parciales de expresiones regulares.

preguntado el 28 de agosto de 11 a las 02:08

No, eso fue un error. Solo lo arreglé. Pulsé por error enviar antes de editar. -

Proporcione también una maqueta del resultado deseado y edite sus entradas para demostrar el problema descrito. -

Para evitar "coincidencias parciales de expresiones regulares", intente ordenar de forma inversa su lista de palabras por longitud: re.compile ("|" .join (sorted (ls_stripped, reverse = True, key = len))) -

twneale: eso no ayuda ... Aún coincidirá incorrectamente con "asset" cuando ls_stripped solo ['set']. La solución correcta es agregar las \ A y \ Z especiales al patrón, pero aún mejor es no usar re en absoluto. -

@bokzor: tienes razón, se necesitarían caracteres especiales. Sin embargo, no estoy de acuerdo en que las expresiones regulares sean categóricamente malas, especialmente cuando simplifican enormemente el código y reducen el texto estándar. -

8 Respuestas

If file2 no es enorme, bébalos en un juego:

file2=set(open("file2").read().split())
for line in open("file1"):
    if line.split(":")[0].strip("'") in file2:
        print line

Respondido 28 ago 11, 07:08

mejor usar set que list aquí. maneja muy bien los duplicados, y puede verificar "in" en un tiempo constante (en lugar de lineal). - Bukzor

Además, desde split() se divide en todos espacio en blanco, si file2 tiene una línea con varias palabras, esto se dividirá en dos (o más) palabras separadas. - Blair

@bukzor, asumo que su archivo2 solo contiene palabras únicas. De lo contrario, sí, un conjunto es mejor. @ Blair, nuevamente, estoy considerando solo una palabra en su archivo2, ya que no hay mención especial en sus ejemplos que contienen varias palabras. De lo contrario, sí, sería mejor dividir en nuevas líneas. - ghostdog74

@ ghostdog74: de hecho, es mejor independientemente de los duplicados. Puede verificar la membresía en un conjunto en tiempo constante, pero una verificación similar en una lista crecerá con la longitud de la lista. - Bukzor

@Blair: de hecho, el OP menciona que él particularmente solo quiere igualar solo palabras y no frases, así que split () parece hacerlo bien. - Bukzor

Supongo que por "mal funcionamiento" te refieres a la velocidad. Porque lo probé y parece funcionar.

Podrías hacer las cosas más eficientes haciendo un set de las palabras en el archivo 2:

word_set = set(ls_stripped)

Y luego en lugar de findall verías si está en el set:

in_set = just_word in word_set

También se siente más limpio que una expresión regular.

Respondido 28 ago 11, 06:08

Bueno, el código devuelve un resultado incorrecto debido a las coincidencias parciales de expresiones regulares. Así que esa es la parte del "mal funcionamiento". El enfoque de expresiones regulares fue una mala idea para empezar ... - Renklauf

Parece que esto puede ser simplemente un caso especial de grep. Si file2 es esencialmente una lista de patrones y el formato de salida es el mismo que file1, entonces es posible que pueda hacer esto:

grep -wf file2 file1

La -w le dice a grep que coincida solo con palabras completas.

Respondido 28 ago 11, 07:08

grep -f =(cat file2 | sed 's/$/\\b/' | sed 's/^/\\b/') file1 para que coincida con palabras completas. - Owen

Esto funciona, siempre que (a) file2 no tiene varias palabras en una línea y (b) grep está disponible en el sistema de destino. - Blair

@Owen: grep -w hace esto mucho mejor. Actualicé la respuesta. - Bukzor

Así es como haría esto. No tengo un intérprete de Python a mano, por lo que puede haber un par de errores tipográficos.

Una de las principales cosas que debe recordar cuando llegue a Python (especialmente si proviene de Perl) es que las expresiones regulares suelen ser una mala idea: los métodos de cadena son poderosos y muy rápidos.

def GetCounts(file1, file2):
    data = {}
    for line in open(file1):
        try:
            word, n = line.rsplit(':', 1)
        except ValueError: # not enough values
            #some kind of input error, go to next line
            continue
        n = int(n.strip())
        if word[0] == word[-1] == "'":
            word = word[1:-1]
        data[word] = n

    for line in open(file2):
        word = line.strip()
        if word[0] == word[-1] == "'":
            word = word[1:-1]
        if word in data:
            print word, data[word]

Respondido 28 ago 11, 06:08

import re, methodcaller

re_target = re.compile(r"^'([a-z]+)': +(\d+)", re.M|re.I)
match_me_contents = open(file2).read().splitlines()
match_me_contents = set(map(methodcaller('strip', "'"), match_me_contents))

res = []
for match in re_target.finditer(open(file1).read()):
    word, value = match.groups()
    if word in match_me_contents:
        res.append((word, value))

Respondido 28 ago 11, 06:08

Mis dos archivos de entrada:

file1.txt:

'WORD': 1
'MULTIPLE WORDS': 1
'OTHER': 2

file2.txt:

'WORD'
'NONEXISTENT'

If file2.txt is garantiza para no tener varias palabras en una línea, no es necesario filtrarlas explícitamente desde el primer archivo. Esto se hará mediante la prueba de membresía:

# Build a set of what words we can return a count for.
with open('file2.txt', 'r') as f:
    allowed_words = set(word.strip() for word in f)

# See which of them exist in the first file.
with open('file1.txt', 'r') as f:
    for line in f:
        word, count = line.strip().split(':')

        # This assumes that strings with a space (multiple words) do not exist in
        # the second file.
        if word in allowed_words:
            print word, count

Y ejecutar esto da:

$ python extract.py
'WORD' 1

If file2.txt puede contener varias palabras, simplemente modifique la prueba en el ciclo:

# Build a set of what words we can return a count for.
with open('file2.txt', 'r') as f:
    allowed_words = set(word.strip() for word in f)

# See which of them exist in the first file.
with open('file1.txt', 'r') as f:
    for line in f:
        word, count = line.strip().split(':')

        # This prevents multiple words from being selected.
        if word in allowed_words and not ' ' in word:
            print word, count

Tenga en cuenta que no me he molestado en quitar las comillas de las palabras. No estoy seguro de si esto es necesario, depende de si se garantiza que la entrada los tenga o no. Sería trivial agregarlos.

Otra cosa que debe considerar es la distinción entre mayúsculas y minúsculas. Si las palabras en minúsculas y mayúsculas deben tratarse de la misma manera, entonces debe convertir todas las entradas a mayúsculas (o minúsculas, no importa cuál) antes de realizar cualquier prueba.

EDITAR: Probablemente sería más eficiente eliminar varias palabras del conjunto de palabras permitidas, en lugar de hacer la verificación en cada línea de file1:

# Build a set of what words we can return a count for.
with open('file2.txt', 'r') as f:
    allowed_words = set(word.strip() for word in f if not ' ' in f)

# See which of them exist in the first file.
with open('file1.txt', 'r') as f:
    for line in f:
        word, count = line.strip().split(':')

        # Check if the word is allowed.
        if word in allowed_words:
            print word, count

Respondido 28 ago 11, 07:08

Esto es lo que se me ocurrió:

def GetCounts(file1, file2):
    target_contents  = open(file1).readlines()  #file 1 as list--> 'WORD': n
    match_me_contents = set(open(file2).read().split('\n'))   #file 2 as list -> 'WORD'  
    for line in target_contents:
        word = line.split(': ')[0]  #get the first item in line.split
        if " " not in word:
            number = line.split(': ')[1]   #get the number associated with the word
            if word in match_me_contents:  
                print word, number

Cambios de su versión:

  • Movido para establecer desde una expresión regular
  • Se dividió en lugar de readlines para deshacerse de las nuevas líneas sin procesamiento adicional
  • Se cambió de dividir la palabra en palabras y verificar si la longitud es una a simplemente verificar si un espacio está en la "palabra" directamente.
    • Sin embargo, esto podría causar un error si el "espacio" no es un espacio real. Esto podría corregirse con una expresión regular para "\ s" o equivalente en su lugar, sin embargo, con una penalización de rendimiento.
  • Se agregó un espacio en line.split (':') para que ese número no tenga un prefijo con un espacio.
    • Esto podría causar un error si no hay un espacio antes del número.
  • emocionado number = line.split(': ')[1] después de la verificación para ver si la palabra contiene espacios para fines de eficiencia, aunque la diferencia de velocidad sería menor (casi con certeza, la mayor parte del tiempo se dedicaría a verificar si un trabajo estaba en el objetivo)

Sin embargo, los errores potenciales solo ocurrirían si la entrada real no está en el formato que presentó.

Respondido 28 ago 11, 07:08

Explotemos la similitud del formato de archivo con la sintaxis de expresión de Python:

from ast import literal_eval
with file("file1") as f:
  word_values = ast.literal_eval('{' + ','.join(line for line in f) + '}')
with file("file2") as f:
  expected_words = set(ast.literal_eval(line) for line in f)
word_values = {k: v for (k, v) in word_values if k in expected_words}

Respondido 28 ago 11, 08:08

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.