Lấy số lượng phần tử trong một trình vòng lặp trong Python


Câu trả lời:


101

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 iteratorkhông xác định cho đến khi bạn lặp qua nó.


14
Thay phiê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ó.
tgray

1
Vì vậy, để xác thực điều hiển nhiên: cách tốt nhất để có được "kích thước" của trình lặp chỉ đơn giản là đếm số lần bạn đã trải qua lần lặp, phải không? Trong trường hợp này, nó sẽ là numIters = 0 ; while iterator: numIters +=1?
Mike Williamson

Thật thú vị, vì vậy đó là vấn đề tạm dừng
Akababa

230

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ì đó.


10
Đối với tôi như thế này thực hiện chính xác những gì OP không muốn làm: lặp qua vòng lặp và đếm.
Adam Crossland

36
Đây là một cách hiệu quả để đếm các yếu tố trong một vòng lặp
Thuyền trưởng Lepton

9
Mặc dù đây không phải là điều OP muốn, vì câu hỏi của anh ta không có câu trả lời, nhưng câu trả lời này tránh được việc lập danh sách và nó nhanh hơn về mặt thực nghiệm bởi một hằng số so với phương pháp rút gọn được liệt kê ở trên.
Phillip Nordwall

5
Không thể giúp đỡ: là _tài liệu tham khảo cho Perl $_? :)
Alois Mahdal

17
@AloisMahdal Không. Thông thường trong Python sử dụng tên _cho một biến giả có giá trị mà bạn không quan tâm.
Taymon

67

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ớ.


vấn đề là tôi đang đọc một tập tin với "pysam" có hàng triệu mục. Pysam trả về một iterator. Để tính toán một số lượng nhất định, tôi cần biết có bao nhiêu lượt đọc trong tệp, nhưng tôi không cần phải đọc từng số ... đó là vấn đề.

6
Tôi không phải là người dùng pysam, nhưng có lẽ nó đang đọc tệp "lười biếng". Nó có ý nghĩa bởi vì bạn không muốn có tệp lớn trong bộ nhớ. Vì vậy, nếu bạn phải biết không. của các bản ghi trước khi lặp, cách duy nhất là tạo hai lần lặp và sử dụng cái đầu tiên để đếm các phần tử và cái thứ hai để đọc tệp. BTW. Đừng sử dụng 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.
Tomasz Wysocki

1
@ user248237: tại sao bạn nói bạn cần biết có bao nhiêu mục có sẵn để tính một số lượng nhất định? Bạn chỉ có thể đọc một lượng cố định của chúng và quản lý trường hợp khi có ít hơn số lượng cố định đó (thực sự đơn giản để sử dụng iterslice). Có một lý do khác để bạn phải đọc tất cả các mục?
kriss

1
@Tomasz Lưu ý rằng giảm không được chấp nhận và sẽ biến mất trong Python 3 trở lên.
Wilduck

7
@Wilduck: Nó chưa biến mất, mới chuyển đếnfunctools.reduce
Daenyth

33

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.izipbằng zip).


3
+1: trong một so sánh thời gian với sum(1 for _ in iterator), điều này nhanh gần gấp đôi.
augustomen

1
Chính xác hơn để nói rằng nó tiêu thụ một lần lặp bằng cách đọc từng mục vào bộ nhớ và loại bỏ nó ngay lập tức.
Rockallite

Điều quan trọng cần lưu ý (mà tôi đã bỏ qua) rằng thứ tự của các đối số là zipvấ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!
Kye W Shi

câu trả lời rất hay sẽ cung cấp tiền thưởng cho nó.
Reut Sharabani

18

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. :)


5
Đây không còn là trường hợp nữa, kể từ PEP 424 và Python 3.4. __length_hint__hiện được ghi lại, nhưng đó là một gợi ý và không đảm bảo tính chính xác.
gsnedder

12

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

Tôi giả sử bạn vẫn có thể lặp qua iterator nếu bạn sử dụng chức năng đó, đúng chứ?
jcollum

12

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:

`` `

1: test_list.py:8: 0.492 KiB

gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))

('danh sách, giây', 1.9684218849870376)

2: test_list_compr.py:8: 0.867 KiB

gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])

('list_compr, giây', 2.5885991149989422)

3: test_sum.py:8: 0.859 KiB

gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()

('tổng, giây', 3.441088170016883)

4: more_itertools / more.py: 413: 1.266 KiB

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)

5: test_reduce.py:8: 0.859 KiB

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


Làm thế nào bạn đo được mức tiêu thụ bộ nhớ?
Normanius

Bạn có thể giải thích tại sao 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 listliê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ử.
Normanius

FYI: Tôi có thể sao chép cho python 3.6.8 (trên MacBookPro) phương thức 1 vượt trội so với các phương thức khác về thời gian chạy (tôi đã bỏ qua phương pháp 4).
Normanius

len(tuple(iterable))thậm chí có thể hiệu quả hơn: bài viết của Nelson Minar
VMAtm

9

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).


2
Một iterator không có gì giống như một danh sách liên kết. Một đối tượng được trả về từ một trình vòng lặp không trỏ đến đối tượng tiếp theo và các đối tượng này không (nhất thiết) được lưu trữ trong bộ nhớ. Thay vào đó, nó có thể mang lại đối tượng lần lượt, dựa trên bất kỳ logic bên trong nào (có thể, nhưng không nhất thiết phải dựa trên danh sách được lưu trữ).
Tom

1
@Tom Tôi đã sử dụng LinkedList làm ví dụ chủ yếu ở chỗ bạn không biết bạn có bao nhiêu vì bạn chỉ biết những gì tiếp theo theo nghĩa (nếu có gì đó). Tôi xin lỗi nếu từ ngữ của tôi có vẻ hơi sai hoặc nếu tôi ngụ ý rằng chúng là một trong cùng một.
Jesus Ramos

8

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.


6

Đ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)

Lưu ý: thử nghiệm này dựa trên python2
Normanius

3

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.


0

Đó là thực tế phổ biến để đưa loại thông tin này vào tiêu đề tệp và để pysam cung cấp cho bạn quyền truy cập vào thông tin này. Tôi không biết định dạng, nhưng bạn đã kiểm tra API chưa?

Như những người khác đã nói, bạn không thể biết chiều dài từ trình vòng lặp.


0

Đ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.


Nó không vi phạm bất cứ điều gì và không có gì sai khi áp dụng kiến ​​thức trước khi sử dụng một trình vòng lặp. Có hàng trăm trình lặp xung quanh, nơi bạn biết rằng số lượng phần tử bị giới hạn. Hãy suy nghĩ về việc chỉ cần lọc một danh sách, bạn có thể dễ dàng đưa ra độ dài tối đa, bạn chỉ không thực sự biết có bao nhiêu yếu tố thực sự phù hợp với điều kiện bộ lọc của bạn. Muốn biết số lượng các yếu tố phù hợp là một ứng dụng hợp lệ, không vi phạm bất kỳ ý tưởng huyền bí nào của một trình vòng lặp.
Michael

0

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

-1
def count_iter(iter):
    sum = 0
    for _ in iter: sum += 1
    return sum

-1

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 copyhoặ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ể lentheo 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

1
Phạm vi không lặp. Có một số loại trình lặp có thể được sao chép, nhưng một số loại khác sẽ khiến mã này bị lỗi với TypeError (ví dụ: trình tạo) và việc lặp qua một trình lặp được sao chép có thể gây ra tác dụng phụ xảy ra hai lần hoặc gây ra sự phá vỡ tùy ý trong mã, đã trả về một mapiterator mong đợi các lệnh gọi hàm kết quả chỉ xảy ra một lần.
user2357112 hỗ trợ Monica
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.