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


답변:


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.