2010-06-16 35 views
85

Tôi có một chuỗi nhiều dòng định nghĩa như thế này:lặp qua các đường dây của một chuỗi

foo = """ 
this is 
a multi-line string. 
""" 

Chuỗi này chúng ta sử dụng như kiểm tra đầu vào cho một phân tích cú pháp Tôi viết thư này. Hàm phân tích cú pháp nhận một file -object làm đầu vào và lặp lại qua nó. Nó cũng gọi phương thức next() trực tiếp để bỏ qua các dòng, vì vậy tôi thực sự cần một trình lặp làm đầu vào, không phải là một lần lặp. Tôi cần một trình lặp mà lặp qua các dòng riêng lẻ của chuỗi đó như một file -object sẽ nằm trên các dòng của một tệp văn bản. Tất nhiên tôi có thể làm như sau:

lineiterator = iter(foo.splitlines()) 

Có cách nào trực tiếp hơn để thực hiện việc này không? Trong trường hợp này, chuỗi phải di chuyển một lần để tách, và sau đó lại phân tích cú pháp. Nó không quan trọng trong trường hợp thử nghiệm của tôi, vì chuỗi rất ngắn ở đó, tôi chỉ yêu cầu tò mò. Python có rất nhiều công cụ hữu dụng và hiệu quả cho những thứ như vậy, nhưng tôi không thể tìm thấy gì phù hợp với nhu cầu này.

+5

bạn biết rằng bạn có thể duyệt qua 'foo.splitlines() 'phải không? – SilentGhost

+0

Bạn có ý nghĩa gì bởi "một lần nữa bởi trình phân tích cú pháp"? – danben

+4

@SilentGhost: Tôi nghĩ rằng vấn đề là không lặp lại chuỗi hai lần. Khi nó được lặp lại bởi 'splitlines()' và lần thứ hai bằng cách lặp qua kết quả của phương thức này. –

Trả lời

105

Dưới đây là ba khả năng:

foo = """ 
this is 
a multi-line string. 
""" 

def f1(foo=foo): return iter(foo.splitlines()) 

def f2(foo=foo): 
    retval = '' 
    for char in foo: 
     retval += char if not char == '\n' else '' 
     if char == '\n': 
      yield retval 
      retval = '' 
    if retval: 
     yield retval 

def f3(foo=foo): 
    prevnl = -1 
    while True: 
     nextnl = foo.find('\n', prevnl + 1) 
     if nextnl < 0: break 
     yield foo[prevnl + 1:nextnl] 
     prevnl = nextnl 

if __name__ == '__main__': 
    for f in f1, f2, f3: 
    print list(f()) 

Chạy này như kịch bản chính khẳng định ba chức năng tương đương. Với timeit (và một * 100 cho foo để có được chuỗi đáng kể để đo chính xác hơn):

$ python -mtimeit -s'import asp' 'list(asp.f3())' 
1000 loops, best of 3: 370 usec per loop 
$ python -mtimeit -s'import asp' 'list(asp.f2())' 
1000 loops, best of 3: 1.36 msec per loop 
$ python -mtimeit -s'import asp' 'list(asp.f1())' 
10000 loops, best of 3: 61.5 usec per loop 

Lưu ý chúng ta cần các list() gọi để đảm bảo vòng lặp nằm ngang, không chỉ xây dựng.

IOW, việc triển khai ngây thơ nhanh hơn rất nhiều, thậm chí còn nhanh hơn 6 lần so với nỗ lực của tôi với các cuộc gọi find, nhanh gấp 4 lần so với phương pháp tiếp cận cấp thấp hơn.

Bài học cần lưu giữ: đo lường luôn là điều tốt (nhưng phải chính xác); các phương thức chuỗi như splitlines được triển khai theo các cách rất nhanh; đặt các chuỗi lại với nhau bằng cách lập trình ở mức rất thấp (đặc biệt là bởi các vòng += các phần rất nhỏ) có thể khá chậm.

Chỉnh sửa: được thêm vào đề xuất của Jacob, được sửa đổi một chút để cung cấp kết quả tương tự như những người khác (dấu trống trên một hàng được giữ), tức là:

from cStringIO import StringIO 

def f4(foo=foo): 
    stri = StringIO(foo) 
    while True: 
     nl = stri.readline() 
     if nl != '': 
      yield nl.strip('\n') 
     else: 
      raise StopIteration 

đo cho:

$ python -mtimeit -s'import asp' 'list(asp.f4())' 
1000 loops, best of 3: 406 usec per loop 

không hoàn toàn tốt như cách tiếp cận dựa .find - vẫn còn, đáng ghi nhớ vì nó có thể là ít bị nhỏ off-by-one lỗi (bất kỳ vòng lặp nào mà bạn thấy các lần xuất hiện +1 và -1, giống như số f3 ở trên của tôi, sẽ tự động kích hoạt các nghi ngờ ngoại tuyến - và vì vậy nhiều vòng lặp thiếu các điều chỉnh như vậy và phải có chúng - mặc dù tôi tin rằng mã của tôi cũng ngay từ khi tôi có thể kiểm tra đầu ra của nó với các chức năng khác ').

Tuy nhiên, cách tiếp cận dựa trên chia tách vẫn còn quy tắc.

Một sang một bên: phong cách thể tốt hơn cho f4 sẽ là:

from cStringIO import StringIO 

def f4(foo=foo): 
    stri = StringIO(foo) 
    while True: 
     nl = stri.readline() 
     if nl == '': break 
     yield nl.strip('\n') 

ít nhất, đó là một chút ít tiết. Sự cần thiết phải loại bỏ dấu vết \n không may thay thế rõ ràng và nhanh hơn vòng lặp while với return iter(stri) (phần iter mà không cần thiết trong các phiên bản Python hiện đại, tôi tin từ 2.3 hoặc 2.4, nhưng nó cũng vô hại). Có lẽ đáng để thử, cũng:

return itertools.imap(lambda s: s.strip('\n'), stri) 

hoặc các biến thể của nó - nhưng tôi dừng ở đây vì nó khá nhiều bài tập lý thuyết wrt các strip dựa, đơn giản nhất và nhanh nhất, người ta.

+0

Ngoài ra, '(dòng [: - 1] cho dòng trong cStringIO.StringIO (foo))' là khá nhanh; gần như nhanh như việc thực hiện ngây thơ, nhưng không hoàn toàn. –

+0

Cảm ơn bạn vì câu trả lời tuyệt vời này. Tôi đoán bài học chính ở đây (vì tôi mới làm quen với python) là sử dụng thói quen 'timeit'. –

+0

@ Không gian, yep, thời gian là tốt, bất kỳ lúc nào bạn quan tâm đến hiệu suất (hãy chắc chắn sử dụng nó một cách cẩn thận, ví dụ như trong trường hợp này thấy ghi chú của tôi về việc cần một cuộc gọi 'list' để thực sự thời gian tất cả các phần liên quan!). –

1

Tôi cho rằng bạn có thể cuộn của riêng bạn:

def parse(string): 
    retval = '' 
    for char in string: 
     retval += char if not char == '\n' else '' 
     if char == '\n': 
      yield retval 
      retval = '' 
    if retval: 
     yield retval 

Tôi không chắc chắn hiệu quả như thế nào thực hiện điều này, nhưng điều đó sẽ chỉ lặp qua chuỗi bạn một lần.

Mmm, máy phát điện.

Chỉnh sửa:

Tất nhiên bạn cũng sẽ muốn thêm bất kỳ loại hành động phân tích nào bạn muốn thực hiện, nhưng điều đó khá đơn giản.

+0

Khá không hiệu quả đối với các dòng dài (phần '+ =' có hiệu suất xấu nhất 'O (N bình phương) ', mặc dù một số thủ thuật triển khai cố gắng giảm xuống khi khả thi). –

+0

Vâng - Gần đây tôi mới biết về điều đó. Nó sẽ được nhanh hơn để nối vào một danh sách các ký tự và sau đó '' .join (ký tự) chúng? Hay đó là một thử nghiệm tôi nên tự thực hiện? ;) –

+0

hãy đo lường chính mình, đó là hướng dẫn - và chắc chắn thử cả hai dòng ngắn như trong ví dụ của OP và các dòng ngắn! -) –

3

Nếu tôi đọc Modules/cStringIO.c một cách chính xác, điều này nên được khá hiệu quả (mặc dù hơi dài dòng):

from cStringIO import StringIO 

def iterbuf(buf): 
    stri = StringIO(buf) 
    while True: 
     nl = stri.readline() 
     if nl != '': 
      yield nl.strip() 
     else: 
      raise StopIteration 
35

Tôi không chắc chắn ý của bạn là gì "sau đó lại phân tích cú pháp". Sau khi quá trình chia nhỏ đã hoàn tất, không có quá trình truyền tải nào khác của chuỗi , chỉ có sự truyền tải của danh sách của các chuỗi phân tách. Điều này có thể thực sự là cách nhanh nhất để thực hiện điều này, miễn là kích thước của chuỗi của bạn không phải là hoàn toàn lớn. Thực tế là python sử dụng các chuỗi không thay đổi có nghĩa là bạn phải luôn tạo một chuỗi mới, vì vậy điều này phải được thực hiện tại một số điểm.

Nếu chuỗi của bạn là rất lớn, bất lợi là sử dụng bộ nhớ: bạn sẽ có chuỗi gốc và danh sách các chuỗi phân chia trong bộ nhớ cùng một lúc, tăng gấp đôi bộ nhớ cần thiết. Một cách tiếp cận vòng lặp có thể giúp bạn tiết kiệm điều này, xây dựng một chuỗi khi cần thiết, mặc dù nó vẫn trả tiền phạt "tách". Tuy nhiên, nếu chuỗi của bạn lớn như vậy, bạn thường muốn tránh ngay cả chuỗi unsplit đang ở trong bộ nhớ. Nó sẽ là tốt hơn chỉ để đọc chuỗi từ một tập tin, mà đã cho phép bạn lặp qua nó như là dòng. Tuy nhiên nếu bạn có một chuỗi ký tự lớn trong bộ nhớ, một cách tiếp cận sẽ là sử dụng StringIO, trình bày giao diện giống như một tệp, bao gồm cho phép lặp lại theo dòng (sử dụng nội bộ .find để tìm dòng mới tiếp theo).). sau đó bạn nhận được:

import StringIO 
s = StringIO.StringIO(myString) 
for line in s: 
    do_something_with(line) 
+0

Đây là những gì tôi tìm kiếm chính xác. – guneysus

1

tìm kiếm Regex dựa trên là đôi khi nhanh hơn so với cách tiếp cận phát:

RRR = re.compile(r'(.*)\n') 
def f4(arg): 
    return (i.group(1) for i in RRR.finditer(arg)) 
+0

Câu hỏi này là về một kịch bản cụ thể, vì vậy sẽ hữu ích khi hiển thị một điểm chuẩn đơn giản, giống như câu trả lời có điểm cao nhất đã làm. –

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