Python'da bir yineleyicide genel olarak, her birini yinelemeden ve saymadan kaç öğenin olduğunu bilmenin etkili bir yolu var mı?
Python'da bir yineleyicide genel olarak, her birini yinelemeden ve saymadan kaç öğenin olduğunu bilmenin etkili bir yolu var mı?
Yanıtlar:
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.
def gen(): yield random.randint(0, 1)sonsuzdur, bu yüzden onu tekrarlayarak bir uzunluk bulamazsınız.
numIters = 0 ; while iterator: numIters +=1mu?
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.
_Perl'lere referans $_mı? :)
_, değerini umursadığınız bir kukla değişkenin adını kullanmak gelenekseldir .
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.
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.
functools.reduce
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).
sum(1 for _ in iterator), bu neredeyse iki kat daha hızlıydı.
zip önemini belirtmek önemlidir : eğer geçerseniz zip(counter, iterable), aslında tekrarlanabilir sayıdan 1 daha fazla alırsınız!
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. :)
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 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:
`
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('liste, saniye', 1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr, sn', 2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('toplam, sn', 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, sn', 9.812256851990242)
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
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.
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).
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.
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)
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.
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.
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
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
mapsonuçta ortaya çıkan işlev çağrılarının yalnızca bir kez gerçekleşmesini bekleyen bir yineleyici döndürdü .