Python'daki bir yineleyicideki öğe sayısını alma


Yanıtlar:


101

Hayır. Mümkün değil.

Misal:

import random

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

iterator = gen(10)

Uzunluğu, iteratorsiz tekrarlayana kadar bilinmiyor.


14
Alternatif olarak, def gen(): yield random.randint(0, 1)sonsuzdur, bu yüzden onu tekrarlayarak bir uzunluk bulamazsınız.
tgray

1
Yani, açık olanı doğrulamak için: bir yineleyicinin "boyutunu" almanın en iyi yolu, yinelemeden kaç kez geçtiğinizi saymaktır, değil mi? Bu durumda, olur numIters = 0 ; while iterator: numIters +=1mu?
Mike Williamson

İlginç, bu durdurma sorunu
Akababa

231

Bu kodun çalışması gerekir:

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

Her öğeyi yinelemesine ve saymasına rağmen, bunu yapmanın en hızlı yolu budur.

Ayrıca, yineleyicinin öğesi olmadığında da çalışır:

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

Tabii ki, sonsuz bir girdi için sonsuza kadar çalışır, bu nedenle yineleyicilerin sonsuz olabileceğini unutmayın:

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

Ayrıca, yineleyicinin bunu yaparak tükeneceğini ve bunu kullanmaya yönelik daha fazla girişimin hiçbir öğe görmeyeceğini unutmayın . Bu, Python yineleyici tasarımının kaçınılmaz bir sonucudur. Elemanları saklamak istiyorsanız, bunları bir listede veya başka bir yerde saklamanız gerekir.


10
Bana OP'nin tam olarak yapmak istemediği şeyi yaptığı gibi geliyor: yineleyici üzerinden yineleme ve sayma.
Adam Crossland

36
Bu, tekrarlanabilir bir öğeyi saymak için alan etkili bir yoldur
Kaptan Lepton

9
OP'nin istediği bu olmasa da, sorusunun bir cevabı olmadığı göz önüne alındığında, bu cevap bir listenin somutlaştırılmasını önler ve yukarıda listelenen azaltma yönteminden bir sabitle ampirik olarak daha hızlıdır.
Phillip Nordwall

5
Yardım edemiyorum: _Perl'lere referans $_mı? :)
Alois Mahdal

17
@AloisMahdal Hayır. Python'da _, değerini umursadığınız bir kukla değişkenin adını kullanmak gelenekseldir .
Taymon

67

Hayır, herhangi bir yöntem her sonucu çözmenizi gerektirir. Yapabilirsin

iter_length = len(list(iterable))

ama bunu sonsuz bir yineleyici üzerinde çalıştırmak elbette asla geri dönmeyecektir. Ayrıca yineleyiciyi tüketecek ve içeriği kullanmak istiyorsanız sıfırlanması gerekecektir.

Hangi gerçek sorunu çözmeye çalıştığınızı bize bildirmek, gerçek hedefinize ulaşmanız için size daha iyi bir yol bulmamıza yardımcı olabilir.

Düzenleme: Kullanarak list()tüm yinelenebilir bir anda belleğe okuyacaktır, bu istenmeyen olabilir. Başka bir yol yapmak

sum(1 for _ in iterable)

başka bir kişinin gönderdiği gibi. Bu onu hafızada tutmaktan kaçınacaktır.


sorun ben milyonlarca girişleri olan "pysam" ile bir dosya okuyorum. Pysam bir yineleyici döndürür. Belirli bir miktarı hesaplamak için, dosyada kaç okuma olduğunu bilmem gerekiyor, ancak her birini okumama gerek yok ... sorun bu.

6
Ben pysam kullanıcısı değilim, ama muhtemelen "tembel" dosyasını okuyor. Mantıklıdır çünkü hafızada büyük bir dosya olmasını istemezsiniz. Yani hayır bilmen gerekiyorsa. Yinelemeden önce kayıtların tek yolu, iki yineleyici oluşturmak ve öğeleri saymak için ilkini ve ikincisini dosya okumak için kullanmaktır. BTW. Kullanmayın len(list(iterable)), tüm verileri belleğe yükler. Sen kullanabilirsiniz: reduce(lambda x, _: x+1, iterable, 0). Düzenleme: Toplam ile Zonda333 kodu da iyidir.
Tomasz Wysocki

1
@ user248237: neden belirli bir miktarı hesaplamak için kaç giriş olduğunu bilmeniz gerektiğini söylüyorsunuz? Sadece sabit bir miktarını okuyabilir ve bu sabit miktardan daha az olduğunda durumu yönetebilirsiniz (iterslice kullanarak yapmak gerçekten basittir). Tüm girişleri okumak için başka bir neden var mı?
kriss

1
@Tomasz Azaltmanın kullanımdan kaldırıldığını ve Python 3 ve sonraki sürümlerde kullanılacağını unutmayın.
Temmuz'da Wilduck

7
@Wilduck: Gitmedi, yeni taşındıfunctools.reduce
Daenyth

33

Yapamazsınız (belirli bir yineleyicinin türü dışında bunu mümkün kılan bazı belirli yöntemler uygular).

Genel olarak yineleyici öğelerini yalnızca yineleyiciyi tüketerek sayabilirsiniz. Muhtemelen en etkili yollardan biri:

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)

(Python'un yerine 3.x itertools.izipile zip).


3
+1: ile karşılaştırıldığında sum(1 for _ in iterator), bu neredeyse iki kat daha hızlıydı.
augustomen

1
Her bir öğeyi belleğe okuyarak ve hemen atarak yinelenebilir bir enerji tükettiğini söylemek daha doğru olur.
Rockallite

(Ki ben gözden kaçırdım ) , argümanlarınzip önemini belirtmek önemlidir : eğer geçerseniz zip(counter, iterable), aslında tekrarlanabilir sayıdan 1 daha fazla alırsınız!
Kye W Shi

çok güzel bir cevap. üzerinde lütuf verirdi.
Reut Sharabani

18

Tür. Sen olabilir kontrol __length_hint__yöntemi, ama bir var (gsnedders yardımsever işaret ettiği gibi, en azından yukarı, Python 3.4) uyarılmalıdır belgesiz uygulama detay ( thread mesajla aşağıdaki ), bu çok iyi kaybolur ya çağırılan burun cinler yerine olabilir.

Aksi takdirde hayır. Yineleyiciler yalnızca next()yöntemi ortaya çıkaran bir nesnedir . İstediğiniz kadar arayabilir ve sonunda yükselebilir veya yükselmeyebilir StopIteration. Neyse ki, bu davranış çoğu zaman kodlayıcı için şeffaftır. :)


5
Bu artık PEP 424 ve Python 3.4'ten itibaren geçerli değildir. __length_hint__şimdi belgelenmiştir, ancak bir ipucudur ve doğruluk garantisi vermez.
gsnedders

12

Bunun için kardinalite paketini beğendim , çok hafif ve yinelemeye bağlı olarak mümkün olan en hızlı uygulamayı kullanmaya çalışıyor.

Kullanımı:

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

Gerçek count()uygulama aşağıdaki gibidir:

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

Bu işlevi kullanırsanız yineleyici üzerinde yineleme yapabileceğinizi varsayıyorum, evet?
jcollum

12

Bu tartışmanın özetini bilmek isteyenler için. Aşağıdakileri kullanarak 50 milyon uzunluğunda bir jeneratör ifadesini saymak için son yüksek puanlar:

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

yürütme performansına göre (bellek tüketimi dahil) sıralanır, sizi şaşırtacak:

`

1: test_list.py:8: 0.492 KiB

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

('liste, saniye', 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, sn', 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()

('toplam, sn', 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, sn', 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)

('azalt, saniye', 13.436614598002052) ``

Yani, len(list(gen))en sık ve daha az bellek tüketilebilir


Bellek tüketimini nasıl ölçtünüz?
normanius

1
Neden len(list(gen))indirgeme yaklaşımından daha az bellek tüketmesi gerektiğini açıklayabilir misiniz ? Birincisi listbellek ayırmayı içeren yeni bir tane oluşturur , ikincisi ise olmamalıdır. Bu yüzden ikincisinin bellekte daha verimli olmasını beklerdim. Ayrıca, bellek tüketimi eleman türüne bağlı olacaktır.
normanius

FYI: Yöntem 1 çalışma zamanı açısından diğer yöntemlerden daha iyi performans gösteren (MacBookPro'da) python 3.6.8 için çoğaltabilirim (yöntem 4'ü atladım).
normanius

len(tuple(iterable))daha da verimli olabilir: Nelson
Minar'ın

9

Yineleyici, yalnızca bir tür arabellek veya akış tarafından okunacak bir sonraki nesneye işaret eden bir nesnedir, aralarında yineleyene kadar kaç şeyiniz olduğunu bilmediğiniz bir LinkedList gibidir. Yineleyiciler verimli olmaları içindir, çünkü yaptıkları tek şey dizinleme kullanmak yerine referanslarla bir sonraki adımın ne olduğunu söylemektir (ancak gördüğünüz gibi bir sonraki kaç girişin olduğunu görme yeteneğini kaybedersiniz).


2
Yineleyici bağlantılı bir listeye benzemez. Bir yineleyiciden döndürülen bir nesne bir sonraki nesneye işaret etmez ve bu nesneler (zorunlu olarak) bellekte depolanmaz. Daha ziyade, hangi iç mantığa bağlı olarak (depolanmış bir listeye dayalı olabilir, ancak olması gerekmez) nesneyi birbiri ardına verebilir.
Tom

1
@TompleList'i örnek olarak kullanıyordum, çünkü ne kadar sahip olduğunuzu bilmiyorsunuz çünkü sadece bir anlamda bir sonraki adımın ne olduğunu biliyorsunuz (eğer bir şey varsa). İfadelerim biraz kapalı görünüyorsa veya aynı olduklarını ima edersem özür dilerim.
Jesus Ramos

8

Orijinal sorunuzla ilgili olarak, cevap hala Python'da bir yineleyicinin uzunluğunu bilmenin hiçbir yolu olmadığıdır.

Sorunuzun pysam kütüphanesinin bir uygulaması tarafından motive edildiği göz önüne alındığında, daha spesifik bir cevap verebilirim: PySAM'a katkıda bulunuyorum ve kesin cevap SAM / BAM dosyalarının tam olarak hizalanmış okuma sayısı sağlamamasıdır. Bu bilgilere bir BAM dizin dosyasından da kolayca erişilemez. Yapabileceğiniz en iyi şey, bir dizi hizalamayı okuduktan ve dosyanın toplam boyutuna göre ekstrapolasyon yaptıktan sonra dosya işaretçisinin konumunu kullanarak yaklaşık hizalama sayısını tahmin etmektir. Bu, bir ilerleme çubuğunu uygulamak için yeterlidir, ancak sabit zamanda hizalamaları sayma yöntemi değildir.


6

Hızlı bir karşılaştırma ölçütü:

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

Sonuçlar:

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

Yani basit count_iter_items gitmek için bir yoldur.

Bunu python3 için ayarlama:

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)

Not: Bu test python2
normanius

3

Bir bilgisayarda "bir şey" uzunluğunu elde etmenin iki yolu vardır.

İlk yol bir sayıyı saklamaktır - bu, dosyayı değiştirmek için dosyaya / verilere dokunan herhangi bir şey gerektirir (veya yalnızca arabirimleri gösteren bir sınıftır - ancak aynı şeye kadar kaynar).

Diğer yol bunun üzerinde yineleme yapmak ve ne kadar büyük olduğunu saymaktır.


0

Bu tür bilgileri dosya başlığına koymak ve pysam'ın size erişmesine izin vermek yaygın bir uygulamadır. Biçimi bilmiyorum, ancak API'yi kontrol ettiniz mi?

Diğerlerinin söylediği gibi, yineleyicinin uzunluğunu bilemezsiniz.


0

Bu, bir nesneye işaretçi olan bir yineleyicinin tanımına ve bir sonraki nesneye nasıl ulaşılacağına ilişkin bilgilere karşıdır.

Bir yineleyici, sona erene kadar kaç kez tekrarlayabileceğini bilmez. Bu sonsuz olabilir, bu yüzden cevabınız sonsuz olabilir.


Hiçbir şeyi ihlal etmiyor ve bir yineleyici kullanırken önceden bilgi uygulamak yanlış bir şey yok. Etrafta element sayısının sınırlı olduğunu bildiğiniz milyonlarca yineleyici var. Sadece bir listeyi filtrelemeyi düşünün, maksimum uzunluğu kolayca verebilirsiniz, kaç öğenin gerçekten filtre durumunuza uyduğunu gerçekten bilmiyorsunuz. Eşleşen öğelerin sayısını bilmek isteyen, yineleyici herhangi bir gizemli fikri ihlal etmeyen geçerli bir uygulamadır.
Michael

0

O davet edildi ne yapacağını genel olarak mümkün değildir; ancak, bitti dile getirilmiştir kaç öğe sayımını için hala çoğu zaman yararlı sonra üzerlerinden iterated sahip. Bunun için jaraco.itertools.Counter veya benzeri kullanabilirsiniz . Paketi yüklemek için Python 3 ve rwt kullanan bir örnek .

$ 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

Muhtemelen, yineleyici olmadan öğelerin sayısını saymak istersiniz, böylece yineleyici tükenmez ve daha sonra tekrar kullanırsınız. Bu copyveya ile mümkündürdeepcopy

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

Çıktı " Finding the length did not exhaust the iterator!"

İsteğe bağlı olarak (ve tavsiye edilmeden), yerleşik lenişlevi aşağıdaki gibi gölgeleyebilirsiniz:

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
Aralıklar yineleyici değil. Kopyalanabilen bazı yineleyici türleri vardır, ancak diğerleri bu kodun bir TypeError (örn. Jeneratörler) ile başarısız olmasına neden olur ve kopyalanan bir yineleyici üzerinden yineleme yapmak yan etkilerin iki kez olmasına neden olabilir veya kodda keyfi olarak kırılmasına neden olabilir, mapsonuçta ortaya çıkan işlev çağrılarının yalnızca bir kez gerçekleşmesini bekleyen bir yineleyici döndürdü .
user2357112 Monica
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.