파이썬에서 이터레이터의 요소 수 얻기


답변:


101

아니요. 불가능합니다.

예:

import random

def gen(n):
    for i in xrange(n):
        if random.randint(0, 1) == 0:
            yield i

iterator = gen(10)

iterator반복 할 때까지 길이를 알 수 없습니다.


14
또는 def gen(): yield random.randint(0, 1)무한대이므로 반복하여 길이를 찾을 수 없습니다.
— tgray

1
따라서 명백한 것을 확인하려면 반복자의 "크기"를 얻는 가장 좋은 방법은 단순히 반복을 거친 횟수를 세는 것입니다. 이 경우에는 numIters = 0 ; while iterator: numIters +=1?
— Mike Williamson

흥미
— 롭기

231

이 코드는 작동해야합니다.

>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50

각 항목을 반복하고 계산하지만 가장 빠른 방법입니다.

반복자에 항목이없는 경우에도 작동합니다.

>>> sum(1 for _ in range(0))
0

물론 무한 입력을 위해 영원히 실행되므로 반복자는 무한 할 수 있습니다.

>>> sum(1 for _ in itertools.count())
[nothing happens, forever]

또한 이 작업을 수행 하면 이터레이터가 소진 될 수 있으며 추가로 시도하면 요소 가 표시 되지 않습니다 . 그것은 파이썬 반복자 디자인의 피할 수없는 결과입니다. 요소를 유지하려면 목록 또는 무언가에 요소를 저장해야합니다.


10
이것이 OP가 원하지 않는 것을 정확하게 수행하는 것처럼 보입니다. 반복자와 반복을 반복합니다.
— Adam Crossland

36
이것은 반복자의 요소를 계산하는 공간 효율적인 방법입니다
— 선장 렙톤

9
OP가 원하는 것은 아니지만 그의 질문에 대한 답변이 없기 때문에이 답변은 목록의 인스턴스화를 피하고 위에 나열된 축소 방법보다 상수에 의해 경험적으로 더 빠릅니다.
— Phillip Nordwall

5
도울 수 없습니다 : _Perl의 참조 $_입니까? :)
— Alois Mahdal

17
@AloisMahdal 아니요. 파이썬에서는 _값이 중요하지 않은 더미 변수 의 이름을 사용하는 것이 일반적입니다 .
— Taymon

67

아니요, 모든 방법을 사용하려면 모든 결과를 해결해야합니다. 넌 할 수있어

iter_length = len(list(iterable))

그러나 무한 반복자에서 실행하면 절대로 반환되지 않습니다. 또한 반복자를 소비하므로 내용을 사용하려면 재설정해야합니다.

해결하려는 실제 문제를 알려 주면 실제 목표를 달성하는 더 좋은 방법을 찾는 데 도움이 될 수 있습니다.

편집 :를 사용 list()하면 전체 iterable을 한 번에 메모리로 읽을 수 있으므로 바람직하지 않습니다. 다른 방법은

sum(1 for _ in iterable)

다른 사람이 게시 한대로 메모리에 보관하지 마십시오.


문제는 수백만 개의 항목이있는 "pysam"파일을 읽고 있다는 것입니다. Pysam은 반복자를 반환합니다. 특정 수량을 계산하려면 파일에 몇 개의 읽기가 있는지 알아야하지만 각각을 읽을 필요는 없습니다. 이것이 문제입니다.

6
나는 pysam 사용자는 아니지만 "lazy"파일을 읽고있을 것입니다. 메모리에 큰 파일을 원하지 않기 때문에 의미가 있습니다. 따라서 당신이 아니오를 알고 있다면. 반복 이전의 레코드 수는 두 개의 반복자를 작성하는 것입니다. 첫 번째 반복자를 사용하여 요소를 계산하고 두 번째 반복자를 사용하여 파일을 읽습니다. BTW. 사용하지 않으면 len(list(iterable))모든 데이터가 메모리에로드됩니다. 사용할 수 있습니다 : reduce(lambda x, _: x+1, iterable, 0). 편집 : 합계 Zonda333 코드도 좋습니다.
— Tomasz Wysocki

1
@ user248237 : 왜 특정 수량을 계산하기 위해 몇 개의 항목을 사용할 수 있는지 알아야합니까? 고정 된 양을 읽고 고정 된 양보다 적은 경우 (iterslice를 사용하는 것이 실제로는 간단 함)를 관리 할 수 ​​있습니다. 모든 항목을 읽어야하는 또 다른 이유가 있습니까?
— kriss

1
@Tomasz reduce는 더 이상 사용되지 않으며 Python 3 이상에서 사라질 것입니다.
— Wilduck

7
@Wilduck : 사라지지 않았습니다.functools.reduce
— Daenyth

33

당신은 할 수 없다 (특정 iterator의 유형이 그것을 가능하게하는 특정 메소드를 구현하는 것을 제외하고).

일반적으로 반복자를 소비하여 반복자 항목 만 계산할 수 있습니다. 아마도 가장 효율적인 방법 중 하나입니다.

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)

(들어 파이썬은 대체 3.X itertools.izip와 함께 zip).


3
+1 :와 시간을 비교 sum(1 for _ in iterator)하면 거의 두 배나 빠릅니다.
— augustomen

1
각 항목을 메모리로 읽고 즉시 버림으로써 iterable을 소비한다고 말하는 것이 더 정확합니다.
— Rockallite

그것은 것을 (I 간과)에 유의해야 인수의 순서 zip문제는 당신이 전달하는 경우 : zip(counter, iterable)당신이 실제로 반복 가능한 수보다 더 많은 일을 얻을 것이다!
— Kye W Shi

아주 좋은 답변입니다. 그것에 현상금을 줄 것입니다.
— Reut Sharabani

18

킨다 메소드를 확인할 수 는__length_hint__ 있지만 (최소한 Python 3.4, gsnedders가 유용하게 지적했듯이) 문서화되지 않은 구현 세부 사항입니다 ( 스레드의 메시지 다음) )이며 대신 비강 악마를 소멸하거나 소환 할 수 .

그렇지 않으면 아닙니다. 반복자는 next()메서드 만 노출하는 개체 일뿐 입니다. 필요에 따라 여러 번 호출 할 수 있으며 결국에는 올리거나 올리지 않을 수 있습니다 StopIteration. 운 좋게도이 동작은 대부분 코더에게 투명합니다. :)


5
PEP 424 와 Python 3.4 에서는 더 이상 그렇지 않습니다 . __length_hint__이제 문서화되었지만 힌트 이며 정확성을 보장하지 않습니다.
— gsnedders

12

나는 카디널리티를 좋아한다 이것을 위해 패키지를 . 그것은 매우 가볍고 iterable에 따라 가능한 가장 빠른 구현을 사용하려고 시도한다.

용법:

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

실제 count()구현은 다음과 같습니다.

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

해당 기능을 사용하면 반복자를 반복 할 수 있다고 가정합니다.
— jcollum

12

따라서 그 토론의 요약을 알고 싶은 사람들을 위해. 최종 최고 점수는 다음을 사용하여 5 천만 길이의 발전기 표현식을 계산합니다.

  • len(list(gen)),
  • len([_ for _ in gen]),
  • sum(1 for _ in gen),
  • ilen(gen)( more_itertool에서 ),
  • reduce(lambda c, i: c + 1, gen, 0),

실행 성능 (메모리 소비 포함)으로 정렬하면 다음과 같이 놀라게됩니다.

```

1 : test_list.py:8 : 0.492 KiB

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

( '목록, 초', 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, sec', 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()

( 'sum, sec', 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, sec', 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)

( '감소, 초', 13.436614598002052)```

따라서 len(list(gen))가장 빈번하고 적은 메모리 소비


메모리 소비는 어떻게 측정 했습니까?
— normanius

1
왜 그런지 설명해 줄 수 있어요 len(list(gen))감소를 기반으로 한 접근 방식보다 적은 메모리를 사용해야하는 ? 전자 list는 메모리 할당과 관련된 새로운 것을 생성 하지만 후자는 메모리 할당을 포함하지 않아야합니다. 따라서 후자는 메모리 효율성이 더 높을 것으로 기대합니다. 또한 메모리 소비는 요소 유형에 따라 다릅니다.
— normanius

참고 : 방법 1이 런타임 측면에서 다른 방법보다 성능이 우수한 python 3.6.8 (MacBookPro)을 재현 할 수 있습니다 (방법 4를 건너 뛰었습니다).
— normanius

len(tuple(iterable)) 훨씬 더 효율적일 수 있습니다. 넬슨 Minar입니다 문서,
— VMAtm

9

반복자는 일종의 버퍼 또는 스트림에서 읽을 다음 객체에 대한 포인터가있는 객체 일뿐입니다. 반복 할 때까지 얼마나 많은 것들을 알지 못하는 LinkedList와 같습니다. 이터레이터는 인덱스를 사용하는 대신 참조로 다음에 나오는 것을 알려주기 때문에 효율적입니다 (그러나 몇 개의 항목이 있는지 볼 수있는 능력을 잃어 버렸습니다).


2
반복자는 연결된 목록과 다릅니다. 반복자에서 반환 된 객체는 다음 객체를 가리 키지 않으며 이러한 객체는 메모리에 (필요하게) 저장되지 않습니다. 오히려 내부 논리가 무엇이든 (저장된 목록을 기반으로 할 수는 있지만 필요하지는 않음) 기반으로 객체를 차례로 생성 할 수 있습니다.
— Tom

1
@Tom 나는 LinkedList를 예제로 주로 사용했습니다. 다음에 무엇을 의미하는지 (무엇이 있는지) 알기 때문에 얼마나 많은 것을 알지 못한다는 것입니다. 내 문구가 약간 어긋나거나 그들이 같은 단어임을 암시하는 경우 사과드립니다.
— 예수 라모스

8

원래의 질문과 관련하여 대답은 여전히 ​​일반적으로 파이썬에서 반복자의 길이를 알 수있는 방법이 없다는 것입니다.

pysam 라이브러리의 응용 프로그램에서 질문에 대한 동기가 부여되면보다 구체적인 답변을 줄 수 있습니다. 저는 PySAM에 기여하고 결정적인 답변은 SAM / BAM 파일이 정확한 정렬 된 읽기 수를 제공하지 않는다는 것입니다. 이 정보는 BAM 인덱스 파일에서도 쉽게 사용할 수 없습니다. 가장 좋은 방법은 여러 정렬을 읽은 ​​후 파일의 전체 크기를 기반으로 외삽 한 후 파일 포인터의 위치를 ​​사용하여 대략적인 정렬 수를 추정하는 것입니다. 이것은 진행률 표시 줄을 구현하기에 충분하지만 일정한 시간에 정렬을 계산하는 방법은 아닙니다.


6

빠른 벤치 마크 :

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

결과 :

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

즉 간단한 count_iter_items가 방법입니다.

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)

참고 :이 시험은 python2을 기반으로
— normanius

3

컴퓨터에서 "무언가"의 길이를 얻는 두 가지 방법이 있습니다.

첫 번째 방법은 카운트를 저장하는 것입니다-파일 / 데이터를 만져서 수정하는 인터페이스가 필요합니다 (또는 인터페이스 만 노출시키는 클래스이지만 똑같은 것으로 요약됩니다).

다른 방법은 그것을 반복하고 얼마나 큰지를 계산하는 것입니다.


0

이 유형의 정보를 파일 헤더에 저장하고 pysam이이 정보에 액세스하는 것이 일반적입니다. 형식을 모르지만 API를 확인하셨습니까?

다른 사람들이 말했듯이 반복자에서 길이를 알 수 없습니다.


0

이것은 객체에 대한 포인터 인 iterator의 정의와 다음 객체에 도달하는 방법에 대한 정보에 위배됩니다.

반복자는 종료 할 때까지 반복 할 수있는 횟수를 모릅니다. 이것은 무한대 일 수 있으므로 무한대가 답이 될 수 있습니다.


그것은 아무것도 위반하지 않으며 반복자를 사용할 때 사전 지식을 적용하는 데 아무런 문제가 없습니다. 요소의 수가 제한되어 있다는 것을 알 수있는 수많은 반복자가 있습니다. 단순히 목록을 필터링하는 것을 고려하면 최대 길이를 쉽게 줄 수 있으며 실제로 얼마나 많은 요소가 필터 조건에 맞는지 알 수 없습니다. 일치하는 요소의 수를 알고 싶은 것은 반복자의 신비한 아이디어를 위반하지 않는 유효한 응용 프로그램입니다.
— Michael

0

일반적으로 요청 된 내용을 수행하는 것은 불가능하지만, 반복 된 후 반복 된 항목 수를 세는 것이 여전히 유용 합니다. 이를 위해 jaraco.itertools.Counter 또는 이와 유사한 것을 사용할 수 있습니다 . 다음은 Python 3 및 rwt 를 사용하여 패키지를로드 하는 예 입니다.

$ 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

아마도 반복기를 사용하지 않고 항목 수를 세어 반복자가 소진되지 않도록하고 나중에 다시 사용할 수 있습니다. 이것은 copy또는deepcopy

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

출력은 "Finding the length did not exhaust the iterator! "입니다

선택적으로 (그리고 바람직하지 않게) 내장 len함수를 다음과 같이 섀도 잉 할 수 있습니다 .

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
범위는 반복자가 아닙니다. 복사 할 수있는 반복자 유형이 있지만 다른 유형으로 인해이 코드가 TypeError (예 : 생성기)로 실패하고 복사 된 반복자를 반복하면 부작용이 두 번 발생하거나 코드에서 임의의 손상이 발생할 수 있습니다. map결과 함수 호출이 한 번만 발생할 것으로 예상 하는 반복자를 반환했습니다 .
— user2357112는
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.