Có một cách hiệu quả để biết có bao nhiêu phần tử trong một trình vòng lặp trong Python, nói chung, mà không lặp qua từng phần và đếm?
Có một cách hiệu quả để biết có bao nhiêu phần tử trong một trình vòng lặp trong Python, nói chung, mà không lặp qua từng phần và đếm?
Câu trả lời:
Không. Không thể.
Thí dụ:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
Độ dài iterator
không xác định cho đến khi bạn lặp qua nó.
def gen(): yield random.randint(0, 1)
là vô hạn, vì vậy bạn sẽ không bao giờ có thể tìm thấy một chiều dài bằng cách lặp qua nó.
numIters = 0 ; while iterator: numIters +=1
?
Mã này sẽ hoạt động:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Mặc dù nó lặp đi lặp lại qua từng mục và đếm chúng, nhưng đó là cách nhanh nhất để làm điều đó.
Nó cũng hoạt động khi iterator không có mục:
>>> sum(1 for _ in range(0))
0
Tất nhiên, nó chạy mãi mãi cho một đầu vào vô hạn, vì vậy hãy nhớ rằng các trình vòng lặp có thể là vô hạn:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
Ngoài ra, hãy lưu ý rằng iterator sẽ cạn kiệt khi thực hiện điều này và những lần thử sử dụng tiếp theo sẽ không thấy yếu tố nào . Đó là hậu quả không thể tránh khỏi của thiết kế trình lặp Python. Nếu bạn muốn giữ các yếu tố, bạn sẽ phải lưu trữ chúng trong một danh sách hoặc thứ gì đó.
_
tài liệu tham khảo cho Perl $_
? :)
_
cho một biến giả có giá trị mà bạn không quan tâm.
Không, bất kỳ phương pháp nào cũng sẽ yêu cầu bạn giải quyết mọi kết quả. Bạn có thể làm
iter_length = len(list(iterable))
nhưng chạy nó trên một trình vòng lặp vô hạn tất nhiên sẽ không bao giờ quay trở lại. Nó cũng sẽ tiêu thụ iterator và nó sẽ cần phải được thiết lập lại nếu bạn muốn sử dụng nội dung.
Nói cho chúng tôi biết vấn đề thực sự bạn đang cố gắng giải quyết có thể giúp chúng tôi tìm cho bạn cách tốt hơn để hoàn thành mục tiêu thực tế của bạn.
Chỉnh sửa: Sử dụng list()
sẽ đọc toàn bộ lần lặp vào bộ nhớ cùng một lúc, điều này có thể không mong muốn. Một cách khác là làm
sum(1 for _ in iterable)
như một người khác đăng. Điều đó sẽ tránh giữ nó trong bộ nhớ.
len(list(iterable))
nó sẽ tải tất cả dữ liệu vào bộ nhớ. Bạn có thể sử dụng : reduce(lambda x, _: x+1, iterable, 0)
. Chỉnh sửa: Mã Zonda333 với tổng cũng tốt.
functools.reduce
Bạn không thể (ngoại trừ loại trình lặp cụ thể thực hiện một số phương thức cụ thể làm cho nó có thể).
Nói chung, bạn chỉ có thể đếm các mục lặp bằng cách tiêu thụ iterator. Một trong những cách hiệu quả nhất:
import itertools
from collections import deque
def count_iter_items(iterable):
"""
Consume an iterable not reading it into memory; return the number of items.
"""
counter = itertools.count()
deque(itertools.izip(iterable, counter), maxlen=0) # (consume at C speed)
return next(counter)
(Đối với Python 3.x thay thế itertools.izip
bằng zip
).
sum(1 for _ in iterator)
, điều này nhanh gần gấp đôi.
zip
vấn đề : nếu bạn vượt qua zip(counter, iterable)
, bạn thực sự sẽ nhận được nhiều hơn 1 lần so với số lần lặp!
Kinda. Bạn có thể kiểm tra __length_hint__
phương thức, nhưng được cảnh báo rằng (ít nhất là lên tới Python 3.4, vì gsnedder chỉ ra một cách hữu ích) đó là một chi tiết triển khai không có giấy tờ ( thông báo sau trong luồng ), thay vào đó có thể biến mất hoặc triệu hồi quỷ mũi.
Mặt khác, không. Các vòng lặp chỉ là một đối tượng chỉ hiển thị next()
phương thức. Bạn có thể gọi nó nhiều lần theo yêu cầu và cuối cùng họ có thể hoặc không thể tăng StopIteration
. May mắn thay, hành vi này là hầu hết thời gian minh bạch cho các lập trình viên. :)
Tôi thích cardinality gói cho việc này, nó rất nhẹ và cố gắng sử dụng triển khai nhanh nhất có thể tùy thuộc vào lần lặp.
Sử dụng:
>>> import cardinality
>>> cardinality.count([1, 2, 3])
3
>>> cardinality.count(i for i in range(500))
500
>>> def gen():
... yield 'hello'
... yield 'world'
>>> cardinality.count(gen())
2
Việc thực count()
hiện như sau:
def count(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
Vì vậy, cho những người muốn biết tóm tắt của cuộc thảo luận đó. Điểm số cao nhất cuối cùng để đếm biểu thức trình tạo dài 50 triệu bằng cách sử dụng:
len(list(gen))
, len([_ for _ in gen])
, sum(1 for _ in gen),
ilen(gen)
(từ more_itertool ),reduce(lambda c, i: c + 1, gen, 0)
, được sắp xếp theo hiệu suất thực thi (bao gồm cả mức tiêu thụ bộ nhớ), sẽ khiến bạn ngạc nhiên:
`` `
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('danh sách, giây', 1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr, giây', 2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('tổng, giây', 3.441088170016883)
d = deque(enumerate(iterable, 1), maxlen=1)
test_ilen.py:10: 0.875 KiB
gen = (i for i in data*1000); t0 = monotonic(); ilen(gen)
('ilen, giây', 9.812256851990242)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('Giảm, giây', 13.436614598002052) `` `
Vì vậy, len(list(gen))
là tiêu thụ bộ nhớ thường xuyên nhất và ít hơn
len(list(gen))
nên tiêu thụ ít bộ nhớ hơn phương pháp dựa trên giảm? Cái trước tạo ra cái mới list
liên quan đến việc cấp phát bộ nhớ trong khi cái trước thì không. Vì vậy, tôi hy vọng cái sau sẽ hiệu quả hơn về bộ nhớ. Ngoài ra, mức tiêu thụ bộ nhớ sẽ phụ thuộc vào loại phần tử.
len(tuple(iterable))
thậm chí có thể hiệu quả hơn: bài viết của Nelson Minar
Trình lặp chỉ là một đối tượng có con trỏ tới đối tượng tiếp theo được đọc bởi một loại bộ đệm hoặc luồng nào đó, nó giống như một Danh sách liên kết nơi bạn không biết bạn có bao nhiêu thứ cho đến khi bạn lặp qua chúng. Các trình vòng lặp có nghĩa là hiệu quả bởi vì tất cả những gì họ làm là cho bạn biết những gì tiếp theo bằng các tham chiếu thay vì sử dụng lập chỉ mục (nhưng như bạn thấy bạn mất khả năng xem có bao nhiêu mục tiếp theo).
Về câu hỏi ban đầu của bạn, câu trả lời vẫn là nói chung không có cách nào để biết độ dài của một trình vòng lặp trong Python.
Cho rằng câu hỏi của bạn được thúc đẩy bởi một ứng dụng của thư viện pysam, tôi có thể đưa ra một câu trả lời cụ thể hơn: Tôi là người đóng góp cho PySAM và câu trả lời dứt khoát là các tệp SAM / BAM không cung cấp số lần đọc chính xác. Thông tin này cũng không dễ dàng có sẵn từ tệp chỉ mục BAM. Cách tốt nhất có thể làm là ước tính số lượng sắp xếp gần đúng bằng cách sử dụng vị trí của con trỏ tệp sau khi đọc một số sắp xếp và ngoại suy dựa trên tổng kích thước của tệp. Điều này là đủ để thực hiện một thanh tiến trình, nhưng không phải là một phương pháp đếm sắp xếp trong thời gian không đổi.
Điểm chuẩn nhanh:
import collections
import itertools
def count_iter_items(iterable):
counter = itertools.count()
collections.deque(itertools.izip(iterable, counter), maxlen=0)
return next(counter)
def count_lencheck(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
def count_sum(iterable):
return sum(1 for _ in iterable)
iter = lambda y: (x for x in xrange(y))
%timeit count_iter_items(iter(1000))
%timeit count_lencheck(iter(1000))
%timeit count_sum(iter(1000))
Kết quả:
10000 loops, best of 3: 37.2 µs per loop
10000 loops, best of 3: 47.6 µs per loop
10000 loops, best of 3: 61 µs per loop
Tức là Count_iter_items đơn giản là con đường để đi.
Điều chỉnh này cho python3:
61.9 µs ± 275 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.4 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
82.6 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Có hai cách để lấy chiều dài của "thứ gì đó" trên máy tính.
Cách đầu tiên là lưu trữ số đếm - điều này đòi hỏi bất cứ thứ gì chạm vào tệp / dữ liệu để sửa đổi nó (hoặc một lớp chỉ hiển thị các giao diện - nhưng nó thực hiện theo cùng một thứ).
Cách khác là lặp đi lặp lại nó và đếm nó lớn như thế nào.
Điều này trái với định nghĩa của một trình vòng lặp, là một con trỏ tới một đối tượng, cộng với thông tin về cách đi đến đối tượng tiếp theo.
Một trình vòng lặp không biết bao nhiêu lần nó sẽ có thể lặp lại cho đến khi chấm dứt. Điều này có thể là vô hạn, vì vậy vô cùng có thể là câu trả lời của bạn.
Mặc dù nói chung không thể thực hiện những gì được yêu cầu, nhưng vẫn rất hữu ích khi đếm được có bao nhiêu mục được lặp đi lặp lại sau khi lặp đi lặp lại chúng. Đối với điều đó, bạn có thể sử dụng jaraco.itertools.Count hoặc tương tự. Đây là một ví dụ sử dụng Python 3 và rwt để tải gói.
$ rwt -q jaraco.itertools -- -q
>>> import jaraco.itertools
>>> items = jaraco.itertools.Counter(range(100))
>>> _ = list(counted)
>>> items.count
100
>>> import random
>>> def gen(n):
... for i in range(n):
... if random.randint(0, 1) == 0:
... yield i
...
>>> items = jaraco.itertools.Counter(gen(100))
>>> _ = list(counted)
>>> items.count
48
Có lẽ, bạn muốn đếm số lượng vật phẩm mà không lặp đi lặp lại, để trình vòng lặp không bị cạn kiệt và bạn sẽ sử dụng lại nó sau. Điều này là có thể với copy
hoặcdeepcopy
import copy
def get_iter_len(iterator):
return sum(1 for _ in copy.copy(iterator))
###############################################
iterator = range(0, 10)
print(get_iter_len(iterator))
if len(tuple(iterator)) > 1:
print("Finding the length did not exhaust the iterator!")
else:
print("oh no! it's all gone")
Đầu ra là "Finding the length did not exhaust the iterator!
"
Tùy chọn (và không được khuyến khích), bạn có thể len
theo dõi hàm tích hợp như sau:
import copy
def len(obj, *, len=len):
try:
if hasattr(obj, "__len__"):
r = len(obj)
elif hasattr(obj, "__next__"):
r = sum(1 for _ in copy.copy(obj))
else:
r = len(obj)
finally:
pass
return r
map
iterator mong đợi các lệnh gọi hàm kết quả chỉ xảy ra một lần.