2017-11-25 137 views
6

Mam plik z dużą ilością odcinków w następującym formacie:Jak szybciej iterować w tym pliku tekstowym?

section_name_1 <attribute_1:value> <attribute_2:value> ... <attribute_n:value> { 
    field_1 finish_num:start_num some_text ; 
    field_2 finish_num:start_num some_text ; 
    ... 
    field_n finish_num:start_num some_text; 
}; 

section_name_2 ... 
... and so on 

Plik może być setki tysięcy linii długich. Liczba atrybutów i pól dla każdej sekcji może być różna. Chciałbym zbudować kilka słowników, aby zachować niektóre z tych wartości. Mam już oddzielny słownik, który zawiera wszystkie możliwe wartości "atrybutu".

import os, re 
from collections import defaultdict 

def mapFile(myFile, attributeMap_d): 
     valueMap_d = {} 
     fieldMap_d = defaultdict(dict) 

     for attributeName in attributeMap_d: 
      valueMap_d[attributeName] = {} 

     count = 0 
     with open(myFile, "rb") as fh: 
      for line in fh: 
       # only look for lines with < 
       if '<' in line: 
        # match all attribute:value pairs inside <> brackets 
        attributeAllMatch = re.findall(r'<(\S+):(\S+)>', line) 
        attributeAllMatchLen = len(attributeAllMatch) 
        count = 0 

        sectionNameMatch = re.match(r'(\S+)\s+<', line) 

        # store each section name and its associated attribute and value into dict 
        for attributeName in attributeMap_d: 
         for element in attributeAllMatch: 
          if element[0] == attributeName: 
           valueMap_d[attributeName][sectionNameMatch.group(1).rstrip()] = element[1].rstrip() 
           count += 1 
         # stop searching if all attributes in section already matched 
         if count == attributeAllMatchLen: break 

        nextLine = next(fh) 

        #in between each squiggly bracket, store all the field names and start/stop_nums into dict 
        #this while loop is very slow... 
        while not "};" in nextLine: 
         fieldMatch = re.search(r'(\S+)\s+(\d+):(\d+)', nextLine) 
         if fieldMatch: 
          fieldMap_d[sectionNameMatch.group(1)][fieldMatch.group(1)] = [fieldMatch.group(2), fieldMatch.group(3)] 
         nextLine = next(fh) 

     return valueMap_d 

Moim problemem jest to, że pętla while, że pasuje do wszystkich wartości pól jest zauważalnie wolniej niż w pozostałej części kodu: 0.5s 2.2s vs. według cProfile jeśli usunąć pętli while. Zastanawiam się, co mogę zrobić, żeby to przyspieszyć.

+1

Możesz użyć generatora z wyrażeniem regularnym - jeśli dostarczysz prawdziwe próbki, możesz być w stanie pomóc ci lepiej. – Jan

+0

Ile to wolniej? –

+0

@Jan Nie mogę dostarczyć oryginalnego pliku, ale zobaczę, czy sam mogę utworzyć próbkę. – Colin

Odpowiedz

2

Regex świetnie się sprawdza, gdy potrzebne jest wymyślne dopasowanie do wzorca, ale gdy nie jest potrzebne, można szybciej analizować tekst za pomocą metod str. Oto kod, który porównuje czas wykonywania analizy pola za pomocą Twojego wyrażenia regularnego, a nie z str.split.

Najpierw tworzę fałszywe dane testowe, które przechowuję na liście rows. Dzięki temu mój kod demo jest prostszy, niż gdybym czytał dane z pliku, ale co ważniejsze, eliminowałby konieczność odczytu plików, dzięki czemu możemy dokładniej porównać szybkość analizowania.

BTW, należy zapisać sectionNameMatch.group(1) poza pętlą analizowania pól, zamiast wywoływać to wywołanie w każdej linii pola.

Po pierwsze, zilustruję, że mój kod poprawnie analizuje dane. :)

import re 
from pprint import pprint 
from time import perf_counter 

# Make some test data 
num = 10 
rows = [] 
for i in range(1, num): 
    j = 100 * i 
    rows.append(' field_{:03} {}:{} some_text here ;'.format(i, j, j - 50)) 
rows.append('};') 
print('\n'.join(rows)) 

# Select whether to use regex to do the parsing or `str.split` 
use_regex = True 
print('Testing {}'.format(('str.split', 'regex')[use_regex])) 

fh = iter(rows) 
fieldMap = {} 

nextLine = next(fh) 
start = perf_counter() 
if use_regex: 
    while not "};" in nextLine: 
     fieldMatch = re.search(r'(\S+)\s+(\d+):(\d+)', nextLine) 
     if fieldMatch: 
      fieldMap[fieldMatch.group(1)] = [fieldMatch.group(2), fieldMatch.group(3)] 
     nextLine = next(fh) 
else: 
    while not "};" in nextLine: 
     if nextLine: 
      data = nextLine.split(maxsplit=2) 
      fieldMap[data[0]] = data[1].split(':') 
     nextLine = next(fh) 

print('time: {:.6f}'.format(perf_counter() - start)) 
pprint(fieldMap) 

wyjście

field_001 100:50 some_text here ; 
field_002 200:150 some_text here ; 
field_003 300:250 some_text here ; 
field_004 400:350 some_text here ; 
field_005 500:450 some_text here ; 
field_006 600:550 some_text here ; 
field_007 700:650 some_text here ; 
field_008 800:750 some_text here ; 
field_009 900:850 some_text here ; 
}; 
Testing regex 
time: 0.001946 
{'field_001': ['100', '50'], 
'field_002': ['200', '150'], 
'field_003': ['300', '250'], 
'field_004': ['400', '350'], 
'field_005': ['500', '450'], 
'field_006': ['600', '550'], 
'field_007': ['700', '650'], 
'field_008': ['800', '750'], 
'field_009': ['900', '850']} 

Oto wyjście z use_regex = False; Nie będę zadawał sobie trud ponownego drukowania danych wejściowych.

Testing str.split 
time: 0.000100 
{'field_001': ['100', '50'], 
'field_002': ['200', '150'], 
'field_003': ['300', '250'], 
'field_004': ['400', '350'], 
'field_005': ['500', '450'], 
'field_006': ['600', '550'], 
'field_007': ['700', '650'], 
'field_008': ['800', '750'], 
'field_009': ['900', '850']} 

Teraz prawdziwy test. Ustawię num = 200000 i skomentuję linie drukujące dane wyjściowe &.

Testing regex 
time: 3.640832 

Testing str.split 
time: 2.480094 

Jak widać, wersja regex jest około 50% wolniejsza.

Te czasy zostały uzyskane na mojej starej 32-bitowej maszynie 2-bitowej z uruchomionym Pythonem 3.6.0, więc twoje prędkości mogą być inne. ;) Jeśli twój Python nie ma time.perf_counter, możesz zamiast tego użyć time.time.

+0

' while not "};" w nextLine: '-> skanuje każdą linię od początku do końca (przynajmniej tak myślę, nie znam Pythona). Zatem może być (nieco) szybsze sprawdzenie tylko pierwszych dwóch bajtów linii. –

+0

@Danny_ds Postanowiłem opuścić tę linię, jak to jest w kodzie OP, ponieważ chciałem skupić się na rzeczach regex, ponieważ to jest główna rzecz, którą można poprawić.Tak, ""}; " w nextLine' wykonuje liniowy skan linii, ale skanowanie przebiega z prędkością C, więc jest to szybsze niż w przypadku próby wyszukania go za pomocą pętli Pythona. Oczywiście, mógłbym po prostu sprawdzić pierwsze 2 znaki linii (nie są bajtami w Pythonie 3, ponieważ używają Unicode do tekstu), np. Za pomocą metody '.startswith', ale wtedy musiałbym założyć nie są spacjami wiodącymi ani nie przesyłają linii przez '.strip' najpierw, aby uciąć spacje. –

+0

Tak, prędkość C i prawdopodobnie już w pamięci podręcznej L1 - dlatego użyłem _ lekko_ :) A użycie '.strip 'prawdopodobnie pogorszyłoby sytuację, w zależności od implementacji. +1 i tak. –