2010-10-12 24 views
6

¿Cómo pasar por bloques de líneas separados por una línea vacía? El archivo tiene el siguiente aspecto:Python: Cómo recorrer bloques de líneas

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 bucle a través de los bloques y apoderarse de los campos de nombre, apellido y edad en una lista de 3 columnas:

Y X 20 
F H 23 
Y S 13 
Z M 25 
+4

¿Qué tienes hasta ahora? – Tim

Respuesta

11

Ésta es otra manera, usando itertools.groupby. La función groupy recorre las líneas del archivo y llama al isa_group_separator(line) por cada line. isa_group_separator devuelve True o False (llamado key) y itertools.groupby luego agrupa todas las líneas consecutivas que produjeron el mismo resultado verdadero o falso.

Esta es una forma muy conveniente de recopilar 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)) 

# Y X 20 
# F H 23 
# Y S 13 
# Z M 25 
0

utilizar un dict, namedtuple o clase personalizada para almacenar cada atributo a medida que lo encuentre, luego anexe el objeto a una lista cuando llegue a una línea en blanco o EOF.

4

Utilice 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 
+0

solo para darle más sabor: diga 'continue' después de reiniciar el acumulador y elimine' else': el mismo flujo de control, pero una sangría menos. es una cuestión de gusto también, el 'rendimiento pendular' debería ser condicional: 'si acumulador: acumulador de rendimiento'; esto evita que se produzcan listas vacías espurias. – flow

5
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 puede ser cambiado en cualquier forma trivial representación de cadena que desee.

+0

Cada vez que intento re.findall() en un código, me da este mensaje de error: File "/usr/lib/python2.6/re.py", línea 177, en findall return _compile (pattern, flags) .findall (cadena) TypeError: string o buffer esperados. ¿Cual es la razon? – Adia

+0

Bueno, el mensaje de error dice que no le está pasando una cadena. Entonces, ¿qué * estás * pasando? –

+0

Gracias Tim, ese está resuelto ahora. – Adia

2

Si el archivo no es muy grande se puede leer todo el archivo con:

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

entonces se puede dividir content a los bloques usando:

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

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

Sin código de comprobación de los datos si el bloque ha requerido 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'])) 
0

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)) 
2

Si el archivo es demasiado grande como para leer en la memoria a la vez, todavía se puede utilizar una solución basada en expresiones regulares mediante el uso de un archivo mapeado de memoria, con el mmap module:

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 simulada" para 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 hace findall()).

Creo que esta solución es demasiado para este caso de uso, sin embargo (aún: es un buen truco para saber ...)

2

itertools importación

# 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 
+0

personalmente tiendo a preferir soluciones legibles ... – flow

+0

Puede convertir las comprensiones en 'for loop' para hacerlo más legible. – Anoop

0

Junto con la media docena de otras soluciones que ya se ve aquí, estoy un poco sorprendido de que nadie ha sido tan ingenuo (es decir, generator-, regex-, MAP, y leer -free) 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 

Vuelva a formatear a gusto.

+0

Hola, Cameron. Este es el Salón Oneliner; aparca tu sorpresa con el camarero al entrar. También puede observar que pocas respuestas, si las hay, incluyen cualquier comprobación de que el archivo que se está leyendo parece remotamente similar al ejemplo del investigador. –

+0

Usted no es el John Machin que calculó pi a 100 lugares a principios del siglo XVIII, ¿verdad? Gracias por la bienvenida. Entiendo tu punto; "Por lo menos, creo que sí ... En la ausencia de divisiones de párrafos con restricciones de comentarios, resumiré de esta manera:" simple "depende de dónde se encuentre uno, y de qué dirección se encuentre. –

1

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

Empecé conociendo dos cosas sobre este problema. Primero, voy a usar 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 que pueda formatear fácilmente la salida.

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

>>> # 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)) 
X Y 20 
H F 23 
S Y 13 
M Z 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")) 
X Y 20 
H F 23 
S Y 13 
M Z 25 
+0

Gracias por la gran respuesta :) – manan

Cuestiones relacionadas