Cách sạch nhất để lấy mục cuối cùng từ trình lặp Python


110

Cách tốt nhất để lấy mục cuối cùng từ trình vòng lặp trong Python 2.6 là gì? Ví dụ, nói

my_iter = iter(range(5))

Ngắn nhất-mã / cách rõ ràng nhất để nhận được là gì 4từ my_iter?

Tôi có thể làm điều này, nhưng nó có vẻ không hiệu quả lắm:

[x for x in my_iter][-1]

4
Các trình lặp lại giả định rằng bạn muốn lặp qua các phần tử và không thực sự truy cập các phần tử cuối cùng. Điều gì ngăn bạn chỉ đơn giản sử dụng phạm vi (5) [- 1]?
Frank

7
@Frank - tôi cho rằng các iterator thực tế là phức tạp hơn và / hoặc xa hơn và / hoặc khó kiểm soát hơniter(range(5))
Chris Lutz

3
@Frank: thực tế là nó thực sự là một hàm trình tạo phức tạp hơn nhiều cung cấp trình lặp. Tôi chỉ đưa ra ví dụ này để nó đơn giản và rõ ràng những gì đang xảy ra.
Peter

4
Nếu bạn muốn mục cuối cùng của một trình lặp, rất có thể bạn đang làm sai. Nhưng câu trả lời là không có bất kỳ cách nào thực sự rõ ràng hơn là lặp qua trình lặp. Điều này là do các trình vòng lặp không có kích thước và trên thực tế, có thể không bao giờ kết thúc, và như vậy có thể không có mục cuối cùng. (Có nghĩa là mã của bạn sẽ chạy mãi mãi, tất nhiên). Vì vậy, câu hỏi kéo dài là: Tại sao bạn muốn mục cuối cùng của một trình lặp?
Lennart Regebro

3
@Peter: Vui lòng cập nhật câu hỏi của bạn. Đừng thêm một loạt các nhận xét vào một câu hỏi của bạn. Vui lòng cập nhật câu hỏi và xóa bình luận.
S.Lott

Câu trả lời:


100
item = defaultvalue
for item in my_iter:
    pass

4
Tại sao trình giữ chỗ "giá trị mặc định"? Tại sao không None? Đây chính xác là những gì Nonedành cho. Bạn có đang gợi ý rằng một số giá trị mặc định của hàm cụ thể thậm chí có thể đúng không? Nếu iterator không thực sự lặp, sau đó là một giá trị out-of-band là nhiều ý nghĩa hơn một số sai lệch chức năng cụ thể mặc định.
S.Lott

46
Giá trị mặc định chỉ là một trình giữ chỗ cho ví dụ của tôi. Nếu bạn muốn sử dụng Nonelàm giá trị mặc định, đó là sự lựa chọn của bạn. Không có không phải lúc nào cũng là mặc định hợp lý nhất và thậm chí có thể không nằm ngoài dải. Cá nhân tôi có xu hướng sử dụng 'defaultvalue = object ()' để đảm bảo rằng đó là một giá trị thực sự duy nhất. Tôi chỉ chỉ ra rằng lựa chọn mặc định nằm ngoài phạm vi của ví dụ này.
Thomas Wouters

28
@ S. Lott: có lẽ nó rất hữu ích để phân biệt sự khác nhau giữa một iterator trống và một iterator có Nonevì nó là giá trị cuối cùng
John La Rooy

8
Có một lỗi thiết kế trong tất cả các trình vòng lặp của tất cả các loại vùng chứa nội trang? Lần đầu tiên tôi đã nghe nói về nó :)
Thomas Wouters

7
Mặc dù đây có lẽ là giải pháp nhanh hơn, nhưng nó phụ thuộc vào biến bị rò rỉ trong vòng lặp for (một tính năng đối với một số, một lỗi đối với những người khác - có thể là FP-guys kinh hoàng). Nhưng dù sao, Guido nói rằng điều này sẽ luôn hoạt động theo cách này, vì vậy nó được xây dựng an toàn để sử dụng.
tokland

68

Sử dụng dequekích thước 1.

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()

6
Đây thực sự là cách nhanh nhất để làm cạn kiệt một chuỗi dài, mặc dù chỉ nhanh hơn vòng lặp for.
Sven Marnach

11
+1 vì đúng về mặt kỹ thuật, nhưng người đọc nên có những cảnh báo thông thường của Python là "Bạn có THỰC SỰ cần tối ưu hóa điều này không?", "Điều này ít rõ ràng hơn, không phải là Pythonic" và "Tốc độ nhanh hơn phụ thuộc vào việc triển khai, điều này có thể thay đổi."
leewz

1
cũng có, nó là một bộ nhớ con heo
Eelco Hoogendoorn

6
@EelcoHoogendoorn Tại sao nó là một bộ nhớ-hog, ngay cả với maxlen là 1?
Chris Wesseling

1
Từ tất cả các giải pháp được trình bày ở đây cho đến nay, tôi thấy đây là giải pháp nhanh nhất và tiết kiệm bộ nhớ nhất .
Markus Strauss

66

Nếu bạn đang sử dụng Python 3.x:

*_, last = iterator # for a better understanding check PEP 448
print(last)

nếu bạn đang sử dụng python 2.7:

last = next(iterator)
for last in iterator:
    continue
print last


Ghi chú bên lề:

Thông thường, giải pháp được trình bày ở trên là những gì bạn cần cho các trường hợp thông thường, nhưng nếu bạn đang xử lý một lượng lớn dữ liệu, sẽ hiệu quả hơn nếu sử dụng dequekích thước 1 ( nguồn )

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()

1
@virtualxtc: Dấu gạch dưới chỉ là một định danh. Ngôi sao phía trước cho biết "mở rộng danh sách". Càng dễ đọc hơn sẽ được *lst, last = some_iterable.
pepr

4
@virtualxtc nope _là biến đặc biệt trong python và được sử dụng để lưu trữ giá trị cuối cùng hoặc nói rằng tôi không quan tâm đến giá trị nên có thể được làm sạch.
DhiaTN

1
Giải pháp Python 3 đó không tiết kiệm bộ nhớ.
Markus Strauss

3
@DhiaTN Vâng, bạn hoàn toàn đúng. Trên thực tế, tôi thích câu thành ngữ Python 3 mà bạn đã trình bày rất nhiều. Tôi chỉ muốn nói rõ rằng nó không hoạt động đối với "dữ liệu lớn". Tôi sử dụng collection.deque cho điều đó, điều này xảy ra nhanh và hiệu quả về bộ nhớ (xem giải pháp từ martin23487234).
Markus Strauss

1
Ví dụ py3.5 + này phải ở trong PEP 448. Tuyệt vời.
EliadL

33

Có lẽ giá trị sử dụng __reversed__nếu nó có sẵn

if hasattr(my_iter,'__reversed__'):
    last = next(reversed(my_iter))
else:
    for last in my_iter:
        pass

27

Đơn giản như:

max(enumerate(the_iter))[1]

8
Ồ, điều này thật thông minh. Không phải là hiệu quả nhất hoặc dễ đọc, nhưng thông minh.
timgeb

6
Vì vậy, chỉ cần suy nghĩ to ra ... Điều này hoạt động vì enumeratetrả về (index, value)như: (0, val0), (1, val1), (2, val2)... và sau đó theo mặc định maxkhi được cung cấp một danh sách các bộ, chỉ so sánh với giá trị đầu tiên của bộ, trừ khi hai giá trị đầu tiên bằng nhau, chúng không bao giờ ở đây vì chúng đại diện cho các chỉ số. Sau đó, chỉ số dưới cùng là bởi vì max trả về toàn bộ (idx, value) trong khi chúng ta chỉ quan tâm đến value. Ý tưởng thú vị.
Taylor Edmiston

21

Điều này không chắc sẽ nhanh hơn vòng lặp for rỗng do lambda, nhưng có thể nó sẽ cung cấp cho người khác một ý tưởng

reduce(lambda x,y:y,my_iter)

Nếu hộp đệm trống, lỗi TypeError sẽ xuất hiện


IMHO, đây là khái niệm trực tiếp nhất. Thay vì nâng cao TypeErrorcho một iterable trống, bạn cũng có thể cung cấp một giá trị mặc định thông qua các giá trị ban đầu của reduce(), ví dụ last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default).
egnha

9

Có cái này

list( the_iter )[-1]

Nếu độ dài của lần lặp lại thực sự hoành tráng - dài đến mức việc cụ thể hóa danh sách sẽ làm cạn kiệt bộ nhớ - thì bạn thực sự cần phải suy nghĩ lại về thiết kế.


1
Đây là giải pháp đơn giản nhất.
laike9m

2
Tốt hơn một chút là sử dụng một bộ tuple.
Christopher Smith,

9
Hoàn toàn không đồng ý với câu cuối cùng. Làm việc với các tập dữ liệu rất lớn (có thể vượt quá giới hạn bộ nhớ nếu được tải tất cả cùng một lúc) là lý do chính để sử dụng trình lặp thay vì danh sách.
Paul

@Paul: một số hàm chỉ trả về một trình lặp. Đây là một cách ngắn và khá dễ đọc để thực hiện trong trường hợp đó (đối với danh sách không sử thi).
serv-inc

Đó là cách kém hiệu quả nhất mà người ta nên tránh vì thói quen xấu không tốt. Một cách khác là sử dụng sắp xếp (dãy) [- 1] để lấy phần tử tối đa của dãy. Vui lòng không bao giờ sử dụng những kẻ xấu số này nếu bạn muốn trở thành kỹ sư phần mềm.
Maksym Ganenko

5

tôi sẽ dùng reversed , ngoại trừ việc nó chỉ lấy các chuỗi thay vì các trình vòng lặp, điều này có vẻ khá tùy ý.

Bất kỳ cách nào bạn làm điều đó, bạn sẽ phải chạy qua toàn bộ trình lặp. Ở mức hiệu quả tối đa, nếu bạn không cần trình lặp lại nữa, bạn chỉ có thể bỏ rác tất cả các giá trị:

for last in my_iter:
    pass
# last is now the last item

Tuy nhiên, tôi nghĩ đây là một giải pháp chưa tối ưu.


4
đảo ngược () không sử dụng một trình lặp, chỉ là các chuỗi.
Thomas Wouters

3
Nó không hề tùy tiện chút nào. Cách duy nhất để đảo ngược một trình lặp là lặp lại đến cuối, trong khi vẫn giữ tất cả các mục trong bộ nhớ. Tôi, e, trước tiên bạn cần tạo ra một trình tự từ nó, trước khi bạn có thể đảo ngược nó. Tất nhiên, điều này sẽ đánh bại mục đích của trình lặp ngay từ đầu, và cũng có nghĩa là bạn đột nhiên sử dụng nhiều bộ nhớ mà không có lý do rõ ràng. Vì vậy, nó đối lập với sự tùy tiện, trên thực tế. :)
Lennart Regebro

@Lennart - Khi tôi nói tùy tiện, nghĩa là tôi khó chịu. Tôi đang tập trung kỹ năng ngôn ngữ của mình vào bài báo của mình sẽ diễn ra trong vài giờ nữa vào buổi sáng.
Chris Lutz

3
Đủ công bằng. Mặc dù IMO sẽ khó chịu hơn nếu nó chấp nhận các trình lặp, bởi vì hầu hết mọi việc sử dụng nó đều là Ý tưởng Xấu (tm). :)
Lennart Regebro

3

Các Toolz thư viện cung cấp một giải pháp tốt đẹp:

from toolz.itertoolz import last
last(values)

Nhưng thêm một phụ thuộc không phải cốt lõi có thể không đáng để chỉ sử dụng nó trong trường hợp này.



0

Tôi sẽ chỉ sử dụng next(reversed(myiter))


8
Lỗi Loại: Lập luận để đảo ngược () phải là một chuỗi
Labo

0

Câu hỏi là về việc lấy phần tử cuối cùng của một trình lặp, nhưng nếu trình lặp của bạn được tạo bằng cách áp dụng các điều kiện cho một trình tự, thì việc đảo ngược có thể được sử dụng để tìm "đầu tiên" của một trình tự đảo ngược, chỉ xem xét các phần tử cần thiết, bằng cách áp dụng đảo ngược với chính trình tự.

Một ví dụ có sẵn,

>>> seq = list(range(10))
>>> last_even = next(_ for _ in reversed(seq) if _ % 2 == 0)
>>> last_even
8

0

Ngoài ra, đối với các trình vòng lặp vô hạn, bạn có thể sử dụng:

from itertools import islice 
last = list(islice(iterator(), 1000))[-1] # where 1000 is number of samples 

Tôi nghĩ rằng nó sẽ chậm hơn sau đó dequenhưng nó nhanh như vậy và nó thực sự nhanh hơn đối với phương pháp vòng lặp (bằng cách nào đó)


-6

Câu hỏi sai và chỉ có thể dẫn đến một câu trả lời phức tạp và không hiệu quả. Để có được một trình lặp, tất nhiên bạn phải bắt đầu từ một thứ có thể lặp lại, điều này trong hầu hết các trường hợp sẽ cung cấp một cách trực tiếp hơn để truy cập vào phần tử cuối cùng.

Khi bạn tạo một trình vòng lặp từ một trình có thể lặp lại, bạn sẽ gặp khó khăn trong việc duyệt qua các phần tử, vì đó là điều duy nhất mà một trình lặp lại cung cấp.

Vì vậy, cách hiệu quả và rõ ràng nhất là không tạo trình lặp ngay từ đầu mà sử dụng các phương thức truy cập gốc của trình lặp.


5
Vậy làm thế nào bạn có được dòng cuối cùng của một tập tin?
Brice M. Dempsey,

@ BriceM.Dempsey Cách tốt nhất không phải là lặp lại toàn bộ (có thể là rất lớn) mà bằng cách chuyển sang kích thước tệp trừ đi 100, đọc 100 byte cuối cùng, quét tìm dòng mới trong đó, nếu không có, hãy truy cập lùi lại 100 byte khác, v.v. Bạn cũng có thể tăng kích thước lùi lại, tùy thuộc vào tình huống của bạn. Đọc một dòng gazillion chắc chắn là một giải pháp không tối ưu.
Alfe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.