2017-11-25 44 views
6

Tôi có một tập tin với rất nhiều phần trong định dạng này:Làm cách nào để lặp qua tệp văn bản này nhanh hơn?

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 

Các tập tin có thể được hàng trăm ngàn dòng dài. Số thuộc tính và trường cho mỗi phần có thể khác nhau. Tôi muốn xây dựng một vài từ điển để giữ một số giá trị này. Tôi có một từ điển riêng biệt đã giữ tất cả các giá trị 'thuộc tính' có thể có.

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 

Vấn đề của tôi là vòng lặp while để phù hợp với tất cả các giá trị trường đáng chú ý là chậm hơn so với phần còn lại của các mã: 0.5s 2.2s vs theo cProfile nếu tôi loại bỏ các vòng lặp while. Tôi tự hỏi tôi có thể làm gì để tăng tốc độ.

+1

Bạn có thể sử dụng trình tạo biểu thức thông thường - nếu bạn cung cấp một số mẫu thực có thể giúp bạn tốt hơn. – Jan

+0

Tốc độ chậm hơn bao nhiêu? –

+0

@ Tôi không thể cung cấp tệp gốc nhưng tôi sẽ xem liệu tôi có thể tự tạo mẫu không. – Colin

Trả lời

2

Regex thật tuyệt khi bạn cần khớp mẫu phù hợp, nhưng khi bạn không cần nó có thể phân tích văn bản nhanh hơn bằng các phương pháp str. Dưới đây là một số mã so sánh thời gian thực hiện phân tích trường bằng cách sử dụng regex của bạn so với việc thực hiện nó với str.split.

Trước tiên, tôi tạo một số dữ liệu thử nghiệm giả mạo mà tôi lưu trữ trong danh sách rows. Việc này làm cho mã demo của tôi đơn giản hơn nếu tôi đọc dữ liệu từ một tệp, nhưng quan trọng hơn, nó giúp loại bỏ chi phí của việc đọc tệp, vì vậy chúng tôi có thể so sánh chính xác hơn tốc độ phân tích cú pháp.

BTW, bạn nên lưu sectionNameMatch.group(1) ngoài vòng lặp phân tích trường, thay vì phải thực hiện cuộc gọi đó trên mọi dòng trường.

Thứ nhất, tôi sẽ minh họa rằng mã của tôi phân tích dữ liệu chính xác. :)

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) 

đầu ra

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']} 

Dưới đây là đầu ra với use_regex = False; Tôi sẽ không làm phiền việc in lại dữ liệu đầu vào.

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']} 

Bây giờ cho thử nghiệm thực. Tôi sẽ đặt num = 200000 và nhận xét các dòng in dữ liệu đầu vào &.

Testing regex 
time: 3.640832 

Testing str.split 
time: 2.480094 

Như bạn có thể thấy, phiên bản regex chậm hơn khoảng 50%.

Thời gian đó được lấy trên máy 32 bit 2GHz cổ của tôi chạy Python 3.6.0, vì vậy tốc độ của bạn có thể khác. ;) Nếu Python của bạn không có time.perf_counter, bạn có thể sử dụng time.time để thay thế.

+0

' trong khi không "};" trong nextLine: '-> điều này sẽ quét từng dòng từ đầu đến cuối (ít nhất là tôi nghĩ vậy, tôi không biết Python). Vì vậy, nó có thể là (hơi) nhanh hơn để chỉ kiểm tra hai byte đầu tiên của dòng. –

+0

@Danny_ds Tôi quyết định rời khỏi dòng đó vì nó nằm trong mã của OP vì tôi muốn tập trung vào các công cụ regex, vì đó là điều chính có thể được cải thiện.Có, '"}; " trong nextLine' thực hiện quét tuyến tính của đường thẳng, nhưng quá trình quét đó chạy ở tốc độ C, vì vậy nó nhanh hơn nếu bạn cố tìm kiếm nó bằng cách sử dụng vòng lặp Python. Chắc chắn, tôi chỉ có thể kiểm tra 2 ký tự đầu tiên của dòng (chúng không phải là byte trong Python 3, vì nó sử dụng Unicode cho văn bản), ví dụ bằng cách sử dụng phương thức '.startswith', nhưng sau đó tôi phải giả sử ở đó không có không gian hàng đầu, hoặc vượt qua dòng thông qua '.strip' trước để cắt bỏ khoảng trắng. –

+0

Có, tốc độ C và có thể đã có trong bộ nhớ cache L1 - đó là lý do tại sao tôi sử dụng _slightly_ :) Và sử dụng '.strip' có thể sẽ làm cho nó thậm chí tệ hơn, tùy thuộc vào việc triển khai. +1 anyway. –

Các vấn đề liên quan