2010-06-06 34 views
9

Giả sử tôi thường xuyên phải làm việc với các tệp có số lượng dòng không xác định, nhưng lớn. Mỗi dòng chứa một tập hợp các số nguyên (dấu cách, dấu phẩy, dấu chấm phẩy hoặc một số ký tự không phải là số phân cách) trong khoảng thời gian đóng [0, R], trong đó R có thể tùy ý lớn. Số lượng số nguyên trên mỗi dòng có thể thay đổi. Thông thường, tôi nhận được cùng số lượng số nguyên trên mỗi dòng, nhưng đôi khi tôi có các dòng có số lượng không bằng nhau.Di chuyển đến vị trí tùy ý trong một tệp bằng Python

Giả sử tôi muốn chuyển đến dòng thứ N trong tệp và truy xuất số K trên dòng đó (và giả sử rằng đầu vào N và K hợp lệ --- tức là, tôi không lo lắng về đầu vào xấu). Làm thế nào để tôi thực hiện điều này một cách hiệu quả trong Python 3.1.2 cho Windows?

Tôi không muốn đi qua từng dòng tệp.

Tôi đã thử sử dụng mmap, nhưng trong khi poking xung quanh ở đây trên SO, tôi đã học được rằng đó có lẽ không phải là giải pháp tốt nhất trên bản dựng 32 bit vì giới hạn 4GB. Và trong sự thật, tôi không thể thực sự tìm ra cách đơn giản di chuyển N dòng ra khỏi vị trí hiện tại của tôi. Nếu tôi có thể ít nhất chỉ cần "nhảy" vào dòng thứ N thì tôi có thể sử dụng .split() và lấy số nguyên K theo cách đó.

Sắc thái ở đây là tôi không chỉ cần lấy một dòng từ tệp. Tôi sẽ cần phải lấy một số dòng: họ không nhất thiết phải tất cả gần nhau, thứ tự mà tôi nhận được chúng quan trọng, và thứ tự không phải lúc nào cũng dựa trên một số chức năng xác định.

Bất kỳ ý tưởng nào? Tôi hy vọng điều này là đủ thông tin.

Cảm ơn!

+0

tôi sẽ rất vui nếu có giải pháp cho trường hợp khi tôi có cùng số lượng số nguyên trên mỗi tệp trong tệp. –

Trả lời

16

Python của seek đi đến một byte bù đắp trong một tập tin, không một dòng bù đắp, đơn giản chỉ vì đó là cách các hệ điều hành hiện đại và hệ thống tập tin của họ hoạt động - hệ điều hành/FS chỉ không ghi hoặc ghi nhớ "bù đắp" theo bất kỳ cách nào, và không có cách nào cho Python (hoặc bất kỳ ngôn ngữ nào khác) để chỉ kỳ diệu đoán chúng. Bất kỳ hoạt động nào nhằm mục đích "đi đến một dòng" chắc chắn sẽ cần phải "đi qua các tập tin" (dưới bìa) để làm cho sự kết hợp giữa số dòng và bù đắp byte.

Nếu bạn đồng ý với điều đó và chỉ muốn ẩn nó khỏi tầm nhìn của bạn, thì giải pháp là mô-đun thư viện chuẩn linecache - nhưng hiệu suất sẽ không tốt hơn mã bạn có thể tự viết.

Nếu bạn cần đọc từ cùng một tệp lớn nhiều lần, tối ưu hóa lớn sẽ chạy một lần trên tệp lớn mà tập lệnh tạo và lưu vào đĩa tương ứng với số dòng từ byte (kỹ thuật) tệp phụ "chỉ mục"); sau đó, tất cả các lần chạy liên tiếp của bạn (cho đến khi các tệp lớn thay đổi) có thể nhanh chóng sử dụng tệp chỉ mục để điều hướng với hiệu suất rất cao thông qua tệp lớn. Đây có phải là trường hợp sử dụng của bạn ...?

Chỉnh sửa: vì dường như điều này có thể áp dụng - đây là ý tưởng chung (mạng kiểm tra cẩn thận, kiểm tra lỗi hoặc tối ưu hóa ;-).Để thực hiện các chỉ số, sử dụng makeindex.py, như sau:

import array 
import sys 

BLOCKSIZE = 1024 * 1024 

def reader(f): 
    blockstart = 0 
    while True: 
    block = f.read(BLOCKSIZE) 
    if not block: break 
    inblock = 0 
    while True: 
     nextnl = block.find(b'\n', inblock) 
     if nextnl < 0: 
     blockstart += len(block) 
     break 
     yield nextnl + blockstart 
     inblock = nextnl + 1 

def doindex(fn): 
    with open(fn, 'rb') as f: 
    # result format: x[0] is tot # of lines, 
    # x[N] is byte offset of END of line N (1+) 
    result = array.array('L', [0]) 
    result.extend(reader(f)) 
    result[0] = len(result) - 1 
    return result 

def main(): 
    for fn in sys.argv[1:]: 
    index = doindex(fn) 
    with open(fn + '.indx', 'wb') as p: 
     print('File', fn, 'has', index[0], 'lines') 
     index.tofile(p) 

main() 

và sau đó sử dụng nó, ví dụ, sau đây useindex.py:

import array 
import sys 

def readline(n, f, findex): 
    f.seek(findex[n] + 1) 
    bytes = f.read(findex[n+1] - findex[n]) 
    return bytes.decode('utf8') 

def main(): 
    fn = sys.argv[1] 
    with open(fn + '.indx', 'rb') as f: 
    findex = array.array('l') 
    findex.fromfile(f, 1) 
    findex.fromfile(f, findex[0]) 
    findex[0] = -1 
    with open(fn, 'rb') as f: 
    for n in sys.argv[2:]: 
     print(n, repr(readline(int(n), f, findex))) 

main() 

Dưới đây là một ví dụ (trên máy tính xách tay chậm của tôi):

$ time py3 makeindex.py kjv10.txt 
File kjv10.txt has 100117 lines 

real 0m0.235s 
user 0m0.184s 
sys 0m0.035s 
$ time py3 useindex.py kjv10.txt 12345 98765 33448 
12345 '\r\n' 
98765 '2:6 But this thou hast, that thou hatest the deeds of the\r\n' 
33448 'the priest appointed officers over the house of the LORD.\r\n' 

real 0m0.049s 
user 0m0.028s 
sys 0m0.020s 
$ 

Tệp mẫu là một tệp văn bản thuần túy của King James 'Kinh thánh:

$ wc kjv10.txt 
100117 823156 4445260 kjv10.txt 

100K dòng, 4,4 MB, như bạn có thể thấy; điều này mất khoảng một phần tư giây để lập chỉ mục và 50 mili giây để đọc và in ra ba dòng ngẫu nhiên (không nghi ngờ điều này có thể được tăng tốc rất nhiều với tối ưu hóa cẩn thận hơn và máy tốt hơn). Chỉ số trong bộ nhớ (và trên đĩa quá) mất 4 byte trên mỗi dòng của textfile được lập chỉ mục, và hiệu suất nên quy mô một cách hoàn hảo tuyến tính, vì vậy nếu bạn có khoảng 100 triệu dòng, 4.4 GB, tôi mong đợi khoảng 4-5 phút để xây dựng chỉ mục, một phút để trích xuất và in ra ba dòng tùy ý (và 400 MB RAM được lấy cho chỉ mục không nên bất tiện ngay cả một máy nhỏ - ngay cả máy tính xách tay chậm chạp của tôi có 2GB sau tất cả ;-). Bạn cũng có thể thấy rằng (đối với tốc độ và tiện lợi), tôi coi tệp là nhị phân (và giả sử mã hóa utf8 - hoạt động với bất kỳ tập con nào như ASCII, ví dụ: tệp văn bản KJ là ASCII) và không bận tâm thu hẹp \r\n thành một ký tự đơn nếu đó là những gì tệp có dạng trình kết thúc dòng (nó khá tầm thường để làm điều đó sau khi đọc từng dòng nếu bạn muốn).

+0

giải pháp tối ưu hóa của bạn là tốt đẹp. tôi thích nó. +1. tôi vẫn phải đi qua các tập tin phụ trợ chỉ mục mặc dù phải không? nhưng tất nhiên, đó là tốt hơn so với việc phải đi qua tập tin ban đầu của tôi. linecache dường như tải toàn bộ tệp vào RAM và tôi sẽ không luôn có sự sang trọng đó. –

+1

@B Rivera, "tệp phụ trợ chỉ mục" phải đủ nhỏ để giữ trong RAM ngay cả đối với một tệp văn bản có vài triệu dòng. Hãy để tôi phác thảo một giải pháp đơn giản để cho thấy những gì tôi có trong tâm trí (tôi sẽ được chỉnh sửa câu trả lời của tôi sớm để hiển thị điều đó). –

+0

bạn biết không, tôi hiểu những gì bạn đang nói. sử dụng linecache trên tệp phụ trợ chỉ mục. điều đó chắc chắn là hợp lý. –

4

Vấn đề là vì các dòng của bạn không có độ dài cố định, bạn phải chú ý đến các điểm đánh dấu dòng để thực hiện tìm kiếm của mình và hiệu quả sẽ "đi ngang qua từng dòng tệp". Do đó, bất kỳ phương pháp khả thi nào vẫn sẽ đi ngang qua tệp, nó chỉ đơn thuần là vấn đề có thể vượt qua nó nhanh nhất.

+0

giả sử tôi nới lỏng yêu cầu về các dòng có chiều dài không bằng nhau. theo chiều dài, bạn có nghĩa là số lượng ký tự thực tế hoặc số nguyên? các tệp được cấu trúc nhiều nhất mà tôi sẽ nhận được là những tệp có cùng số nguyên được phân tách bằng dấu phân cách trên mỗi dòng, nhưng chúng sẽ không nhất thiết có cùng số ký tự. điều đó làm cho nó có thể làm những gì tôi cần? –

+0

@BRivera, vấn đề là số byte trên mỗi dòng - cách các byte đó trong một dòng được chia ra giữa các số nguyên hoặc bất kỳ thứ gì khác không liên quan. –

+2

Giả sử chúng là các tệp văn bản (và giả sử tệp được mã hóa bằng mã hóa ký tự số byte cố định, chẳng hạn như ASCII thuần túy), "chiều dài bằng nhau" có nghĩa là số ký tự bằng nhau. Lý do tại sao nó tạo ra sự khác biệt là vì nếu bạn biết rằng các dòng có độ dài * ký tự cố định *, bạn cũng biết rằng các dòng có độ dài * byte * cố định và do đó bạn có thể sử dụng 'file.seek (linelength * (linenum-1)) 'để di chuyển đến byte bắt đầu một dòng nhất định. Nhưng điều này chỉ hoạt động nếu 'linelength' giống nhau cho tất cả các dòng. Nếu không, tính toán đó là không thể. – Amber

0

Một giải pháp khác, nếu tệp có khả năng thay đổi rất nhiều, hãy chuyển sang cơ sở dữ liệu thích hợp. Công cụ cơ sở dữ liệu sẽ tạo và duy trì các chỉ mục cho bạn để bạn có thể thực hiện tìm kiếm/truy vấn rất nhanh.

Điều này có thể là quá mức cần thiết.

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