Python: Cómo recorrer bloques de líneas

¿Cómo pasar por bloques de líneas separadas por una línea vacía? El archivo se parece a lo siguiente:

ID: 1 Name: X FamilyN: Y Age: 20 ID: 2 Name: H FamilyN: F Age: 23 ID: 3 Name: S FamilyN: Y Age: 13 ID: 4 Name: M FamilyN: Z Age: 25 

Quiero recorrer los bloques y tomar los campos Nombre, Apellido y Edad en una lista de 3 columnas:

 YX 20 FH 23 YS 13 ZM 25 

Aquí hay otra manera, usando itertools.groupby . La función groupy itera a través de las líneas del archivo y llama a isa_group_separator(line) para cada line . isa_group_separator devuelve Verdadero o Falso (llamado key ), e itertools.groupby agrupa todas las líneas consecutivas que dieron el mismo resultado Verdadero o Falso.

Esta es una manera muy conveniente de reunir líneas en grupos.

 import itertools def isa_group_separator(line): return line=='\n' with open('data_file') as f: for key,group in itertools.groupby(f,isa_group_separator): # print(key,list(group)) # uncomment to see what itertools.groupby does. if not key: data={} for item in group: field,value=item.split(':') value=value.strip() data[field]=value print('{FamilyN} {Name} {Age}'.format(**data)) # YX 20 # FH 23 # YS 13 # ZM 25 
 import re result = re.findall( r"""(?mx) # multiline, verbose regex ^ID:.*\s* # Match ID: and anything else on that line Name:\s*(.*)\s* # Match name, capture all characters on this line FamilyN:\s*(.*)\s* # etc. for family name Age:\s*(.*)$ # and age""", subject) 

El resultado será entonces

 [('X', 'Y', '20'), ('H', 'F', '23'), ('S', 'Y', '13'), ('M', 'Z', '25')] 

que se puede cambiar trivialmente en cualquier representación de cadena que desee.

Usa un generador.

 def blocks( iterable ): accumulator= [] for line in iterable: if start_pattern( line ): if accumulator: yield accumulator accumulator= [] # elif other significant patterns else: accumulator.append( line ) if accumulator: yield accumulator 

Si su archivo es demasiado grande para leerlo en la memoria de una vez, aún puede usar una solución basada en expresiones regulares usando un archivo mapeado en memoria, con el módulo mmap :

 import sys import re import os import mmap block_expr = re.compile('ID:.*?\nAge: \d+', re.DOTALL) filepath = sys.argv[1] fp = open(filepath) contents = mmap.mmap(fp.fileno(), os.stat(filepath).st_size, access=mmap.ACCESS_READ) for block_match in block_expr.finditer(contents): print block_match.group() 

El truco de mmap proporcionará una “cadena de simulación” para hacer que las expresiones regulares funcionen en el archivo sin tener que leerlo todo en una cadena grande. Y el método find_iter() del objeto de expresión regular producirá coincidencias sin crear una lista completa de todas las coincidencias a la vez (lo que findall() hace).

Sin embargo, creo que esta solución es excesiva para este caso de uso (aún así: es un buen truco para saber …)

importar itertools

 # Assuming input in file input.txt data = open('input.txt').readlines() records = (lines for valid, lines in itertools.groupby(data, lambda l : l != '\n') if valid) output = [tuple(field.split(':')[1].strip() for field in itertools.islice(record, 1, None)) for record in records] # You can change output to generator by output = (tuple(field.split(':')[1].strip() for field in itertools.islice(record, 1, None)) for record in records) # output = [('X', 'Y', '20'), ('H', 'F', '23'), ('S', 'Y', '13'), ('M', 'Z', '25')] #You can iterate and change the order of elements in the way you want # [(elem[1], elem[0], elem[2]) for elem in output] as required in your output 

Si el archivo no es enorme, puedes leer el archivo completo con:

 content = f.open(filename).read() 

entonces puedes dividir el content en bloques usando:

 blocks = content.split('\n\n') 

Ahora puedes crear una función para analizar el bloque de texto. str.strip() split('\n') para obtener líneas de bloque y split(':') para obtener clave y valor, eventualmente con str.strip() o alguna ayuda de expresiones regulares.

Sin verificar si el bloque ha requerido el código de datos puede verse como:

 f = open('data.txt', 'r') content = f.read() f.close() for block in content.split('\n\n'): person = {} for l in block.split('\n'): k, v = l.split(': ') person[k] = v print('%s %s %s' % (person['FamilyN'], person['Name'], person['Age'])) 

Esta respuesta no es necesariamente mejor que lo que ya se ha publicado, pero como ilustración de cómo enfoco problemas como este, puede ser útil, especialmente si no está acostumbrado a trabajar con el intérprete interactivo de Python.

Empecé sabiendo dos cosas sobre este problema. Primero, voy a utilizar itertools.groupby para agrupar la entrada en listas de líneas de datos, una lista para cada registro de datos individual. En segundo lugar, quiero representar esos registros como diccionarios para poder formatear fácilmente la salida.

Otra cosa que esto muestra es cómo el uso de generadores hace que sea fácil dividir un problema como este en partes pequeñas.

 >>> # first let's create some useful test data and put it into something >>> # we can easily iterate over: >>> data = """ID: 1 Name: X FamilyN: Y Age: 20 ID: 2 Name: H FamilyN: F Age: 23 ID: 3 Name: S FamilyN: Y Age: 13""" >>> data = data.split("\n") >>> # now we need a key function for itertools.groupby. >>> # the key we'll be grouping by is, essentially, whether or not >>> # the line is empty. >>> # this will make groupby return groups whose key is True if we >>> care about them. >>> def is_data(line): return True if line.strip() else False >>> # make sure this really works >>> "\n".join([line for line in data if is_data(line)]) 'ID: 1\nName: X\nFamilyN: Y\nAge: 20\nID: 2\nName: H\nFamilyN: F\nAge: 23\nID: 3\nName: S\nFamilyN: Y\nAge: 13\nID: 4\nName: M\nFamilyN: Z\nAge: 25' >>> # does groupby return what we expect? >>> import itertools >>> [list(value) for (key, value) in itertools.groupby(data, is_data) if key] [['ID: 1', 'Name: X', 'FamilyN: Y', 'Age: 20'], ['ID: 2', 'Name: H', 'FamilyN: F', 'Age: 23'], ['ID: 3', 'Name: S', 'FamilyN: Y', 'Age: 13'], ['ID: 4', 'Name: M', 'FamilyN: Z', 'Age: 25']] >>> # what we really want is for each item in the group to be a tuple >>> # that's a key/value pair, so that we can easily create a dictionary >>> # from each item. >>> def make_key_value_pair(item): items = item.split(":") return (items[0].strip(), items[1].strip()) >>> make_key_value_pair("a: b") ('a', 'b') >>> # let's test this: >>> dict(make_key_value_pair(item) for item in ["a:1", "b:2", "c:3"]) {'a': '1', 'c': '3', 'b': '2'} >>> # we could conceivably do all this in one line of code, but this >>> # will be much more readable as a function: >>> def get_data_as_dicts(data): for (key, value) in itertools.groupby(data, is_data): if key: yield dict(make_key_value_pair(item) for item in value) >>> list(get_data_as_dicts(data)) [{'FamilyN': 'Y', 'Age': '20', 'ID': '1', 'Name': 'X'}, {'FamilyN': 'F', 'Age': '23', 'ID': '2', 'Name': 'H'}, {'FamilyN': 'Y', 'Age': '13', 'ID': '3', 'Name': 'S'}, {'FamilyN': 'Z', 'Age': '25', 'ID': '4', 'Name': 'M'}] >>> # now for an old trick: using a list of column names to drive the output. >>> columns = ["Name", "FamilyN", "Age"] >>> print "\n".join(" ".join(d[c] for c in columns) for d in get_data_as_dicts(data)) XY 20 HF 23 SY 13 MZ 25 >>> # okay, let's package this all into one function that takes a filename >>> def get_formatted_data(filename): with open(filename, "r") as f: columns = ["Name", "FamilyN", "Age"] for d in get_data_as_dicts(f): yield " ".join(d[c] for c in columns) >>> print "\n".join(get_formatted_data("c:\\temp\\test_data.txt")) XY 20 HF 23 SY 13 MZ 25 

Use un dict, nameduuple o clase personalizada para almacenar cada atributo cuando lo encuentre, luego agregue el objeto a una lista cuando llegue a una línea en blanco o EOF.

Solución simple:

 result = [] for record in content.split('\n\n'): try: id, name, familyn, age = map(lambda rec: rec.split(' ', 1)[1], record.split('\n')) except ValueError: pass except IndexError: pass else: result.append((familyn, name, age)) 

Junto con la media docena de otras soluciones que ya veo aquí, estoy un poco sorprendido de que nadie haya sido tan simple (es decir, generador, regex, mapa y lectura libre) como para proponer, por ejemplo,

 fp = open(fn) def get_one_value(): line = fp.readline() if not line: return None parts = line.split(':') if 2 != len(parts): return '' return parts[1].strip() # The result is supposed to be a list. result = [] while 1: # We don't care about the ID. if get_one_value() is None: break name = get_one_value() familyn = get_one_value() age = get_one_value() result.append((name, familyn, age)) # We don't care about the block separator. if get_one_value() is None: break for item in result: print item 

Re-formato al gusto.