2010-10-12 3 views
6

Jak przejść przez bloki linii oddzielone pustą linią? Plik wygląda następująco:Python: Jak przechodzić przez bloki linii

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 

Chcę pętli poprzez bloki i chwycić pola imię, nazwisko i wiek w wykazie 3 kolumny:

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

Co masz do tej pory? – Tim

Odpowiedz

11

Oto kolejny sposób, używając itertools.groupby. Funkcja groupy iteruje po liniach pliku i wywołuje isa_group_separator(line) dla każdego line. isa_group_separator zwraca wartość True lub False (zwaną key) i itertools.groupby, a następnie grupuje wszystkie kolejne wiersze, które dały taki sam wynik Prawdy lub Fałszy.

To bardzo wygodny sposób na zbieranie linii w grupy.

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

użyć DICT, namedtuple lub klasa niestandardowa do przechowywania każdego atrybutu w momencie jego napotkania, a następnie dołączenie obiektu do listy po osiągnięciu pustej linii lub EOF.

4

Użyj generatora.

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

tylko po to, aby urozmaicić odrobinę: powiedz "kontynuuj" po ponownym zainicjowaniu akumulatora i wyjmij "else": ten sam przepływ sterowania, ale jedno mniej wcięcia. to kwestia gustu. także "zwisająca wydajność" powinna być warunkowa: "jeśli akumulator: wydajność akumulatora"; to pozwala uniknąć fałszywych pustych list. – 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) 

Rezultat będzie wtedy

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

które mogą być trywialnie zmienił się cokolwiek ciąg reprezentacja chcesz.

+0

Za każdym razem, gdy próbuję re.findall() w kodzie, daje mi ten komunikat o błędzie: Plik "/usr/lib/python2.6/re.py", wiersz 177, w findall return _compile (wzór, flagi) .findall (string) TypeError: oczekiwany ciąg lub bufor. Jaki jest powód? – Adia

+0

Cóż, komunikat o błędzie mówi, że nie przekazujesz do niego ciągu znaków. Więc czym * jesteś * przechodzisz do niego? –

+0

Dzięki Tim, ten jest już rozwiązany. – Adia

2

Jeśli plik nie jest ogromna, można odczytać cały plik z:

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

następnie można podzielić content bloków przy użyciu:

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

Teraz można utworzyć funkcję do analizowania blok tekstu. Chciałbym użyć split('\n'), aby uzyskać linie z bloku i split(':'), aby uzyskać klucz i wartość, ewentualnie z str.strip() lub pomocą wyrażeń regularnych.

bez sprawdzania czy jest wymagane blok kodu danych może wyglądać następująco:

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

proste rozwiązanie:

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

Jeśli plik jest zbyt duży, aby czytać w pamięci na raz, można nadal użyj rozwiązania opartego na wyrażeń regularnych, korzystając z pliku odwzorowanego w pamięci, z : Sztuczka mmap zapewni "ciąg do udawania", który spowoduje, że wyrażenia regularne będą działać na pliku, bez konieczności odczytywania wszystkiego w jeden duży ciąg. I metoda find_iter() obiektu wyrażenia regularnego przyniesie dopasowania bez tworzenia całej listy wszystkich dopasowań na raz (co robi findall()).

uważam, że to rozwiązanie jest przesadą dla tego przypadku użycia jednak (jeszcze: jest to miły podstęp wiedzieć ...)

2

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

Osobiście staram się preferować czytelne rozwiązania ... – flow

+0

Konwertuje zrozumienie na "pętlę for", aby było bardziej czytelne. – Anoop

0

Wraz z pół tuzina innych rozwiązań już widzę tutaj, jestem nieco zaskoczony, że nikt nie był tak naiwny (czyli generator-, regex-, Mapa- i czytaj -free), aby zaproponować, na przykład,

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 

Ponownie sformatować do smaku.

+0

Cześć, Cameron. To jest salon Oneliner; zaparkuj niespodziankę barmanem przy wejściu. Możesz również zauważyć, że kilka odpowiedzi, jeśli nie, obejmuje sprawdzenie, czy odczytany plik jest nawet trochę podobny do przykładu osoby pytającej. –

+0

Nie jesteś John Machin, który obliczył pi do 100 miejsc na początku XVIII wieku, prawda? Dzięki za powitanie. Wiem o co ci chodzi; "najmniej, myślę, że robię ... W ograniczonym komentarzem braku podziałów akapitu, podsumuję w ten sposób:" prosty "zależy od tego, gdzie się znajduje i w którą stronę się zwraca. –

1

Ta odpowiedź niekoniecznie jest lepsza niż ta, która została już opublikowana, ale jako ilustracja tego, jak podchodzę do takich problemów, może być przydatna, zwłaszcza jeśli nie jesteś przyzwyczajony do pracy z interaktywnym interpreterem Pythona.

Zacząłem od poznania dwóch rzeczy na temat tego problemu. Najpierw zamierzam użyć itertools.groupby, aby pogrupować dane wejściowe w listy linii danych, po jednej liście dla każdego rekordu danych. Po drugie, chcę reprezentować te rekordy jako słowniki, dzięki czemu mogę łatwo formatować dane wyjściowe.

Jeszcze jedna rzecz, która pokazuje, że używanie generatorów sprawia, że ​​łatwiej jest zepsuć taki problem na małe części.

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

Dzięki za wspaniałą odpowiedź :) – manan