Apakah ada cara yang efisien untuk mengetahui berapa banyak elemen yang ada dalam sebuah iterator dengan Python, secara umum, tanpa melakukan iterasi melalui setiap dan penghitungan?
Apakah ada cara yang efisien untuk mengetahui berapa banyak elemen yang ada dalam sebuah iterator dengan Python, secara umum, tanpa melakukan iterasi melalui setiap dan penghitungan?
Jawaban:
Tidak. Itu tidak mungkin.
Contoh:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
Panjangnya iterator
tidak diketahui sampai Anda mengulanginya.
def gen(): yield random.randint(0, 1)
tidak terbatas, jadi Anda tidak akan pernah bisa menemukan panjang dengan mengulanginya.
numIters = 0 ; while iterator: numIters +=1
?
Kode ini seharusnya berfungsi:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
Meskipun melakukan iterasi melalui setiap item dan menghitungnya, ini adalah cara tercepat untuk melakukannya.
Ini juga berfungsi jika iterator tidak memiliki item:
>>> sum(1 for _ in range(0))
0
Tentu saja, ini berjalan selamanya untuk input tak terbatas, jadi ingatlah bahwa iterator bisa tak terbatas:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
Juga, ketahuilah bahwa iterator akan habis dengan melakukan ini, dan upaya lebih lanjut untuk menggunakannya tidak akan melihat elemen . Itu adalah konsekuensi yang tidak dapat dihindari dari desain iterator Python. Jika Anda ingin menyimpan elemen, Anda harus menyimpannya dalam daftar atau semacamnya.
_
referensi ke Perl $_
? :)
_
untuk variabel dummy yang nilainya tidak Anda pedulikan.
Tidak, metode apa pun akan mengharuskan Anda menyelesaikan setiap hasil. Anda dapat melakukan
iter_length = len(list(iterable))
tetapi menjalankannya pada iterator tak terbatas tentu saja tidak akan pernah kembali. Ini juga akan menggunakan iterator dan perlu diatur ulang jika Anda ingin menggunakan isinya.
Memberi tahu kami masalah nyata yang Anda coba selesaikan dapat membantu kami menemukan cara yang lebih baik untuk mencapai tujuan Anda yang sebenarnya.
Sunting: Menggunakan list()
akan membaca seluruh iterable ke dalam memori sekaligus, yang mungkin tidak diinginkan. Cara lain adalah melakukannya
sum(1 for _ in iterable)
seperti yang diposting orang lain. Itu akan menghindari menyimpannya dalam memori.
len(list(iterable))
itu akan memuat semua data ke memori. Anda dapat menggunakan: reduce(lambda x, _: x+1, iterable, 0)
. Edit: Kode Zonda333 dengan jumlah juga bagus.
functools.reduce
Anda tidak bisa (kecuali tipe iterator tertentu mengimplementasikan beberapa metode spesifik yang memungkinkannya).
Umumnya, Anda dapat menghitung item iterator hanya dengan menggunakan iterator. Salah satu cara yang mungkin paling efisien:
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)
(Untuk Python 3.x ganti itertools.izip
dengan zip
).
sum(1 for _ in iterator)
, ini hampir dua kali lebih cepat.
zip
penting : jika Anda lulus zip(counter, iterable)
, Anda benar-benar akan mendapatkan 1 lebih banyak daripada jumlah yang dapat diulang!
Agak. Anda dapat memeriksa __length_hint__
metodenya, tetapi diperingatkan bahwa (setidaknya hingga Python 3.4, seperti yang ditunjukkan oleh gsnedders dengan sangat membantu) ini adalah detail implementasi yang tidak terdokumentasi ( mengikuti pesan di utas ), yang dapat menghilang dengan baik atau memanggil setan hidung sebagai gantinya.
Jika tidak, tidak. Iterator hanyalah sebuah objek yang hanya mengekspos next()
metode tersebut. Anda dapat memanggilnya sebanyak yang diperlukan dan mereka mungkin atau mungkin tidak akan menaikkannya StopIteration
. Untungnya, perilaku ini sering kali transparan bagi pembuat kode. :)
Nah, bagi yang ingin mengetahui ringkasan pembahasan itu. Skor teratas akhir untuk menghitung ekspresi generator berdurasi 50 juta menggunakan:
len(list(gen))
, len([_ for _ in gen])
, sum(1 for _ in gen),
ilen(gen)
(dari more_itertool ),reduce(lambda c, i: c + 1, gen, 0)
, diurutkan berdasarkan performa eksekusi (termasuk konsumsi memori), akan membuat Anda terkejut:
``
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('list, sec', 1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr, detik', 2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('jumlah, detik', 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, detik', 9.812256851990242)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('kurangi, detik', 13.436614598002052) ``
Jadi, len(list(gen))
adalah yang paling sering dan lebih sedikit memori yang dikonsumsi
len(list(gen))
harus mengonsumsi lebih sedikit memori daripada pendekatan berdasarkan pengurangan? Yang pertama membuat yang baru list
yang melibatkan alokasi memori sedangkan yang terakhir seharusnya tidak. Jadi saya berharap yang terakhir lebih hemat memori. Selain itu, konsumsi memori akan bergantung pada jenis elemen.
len(tuple(iterable))
bahkan bisa lebih efisien: artikel oleh Nelson Minar
Saya suka paket kardinalitas untuk ini, ini sangat ringan dan mencoba menggunakan implementasi tercepat yang tersedia tergantung pada iterable.
Pemakaian:
>>> 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()
Implementasi sebenarnya adalah sebagai berikut:
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
Sebuah iterator hanyalah sebuah objek yang memiliki penunjuk ke objek berikutnya untuk dibaca oleh semacam buffer atau aliran, ini seperti LinkedList di mana Anda tidak tahu berapa banyak hal yang Anda miliki sampai Anda mengulanginya. Iterator dimaksudkan agar efisien karena yang mereka lakukan hanyalah memberi tahu Anda apa yang berikutnya dengan referensi alih-alih menggunakan pengindeksan (tetapi seperti yang Anda lihat, Anda kehilangan kemampuan untuk melihat berapa banyak entri berikutnya).
Mengenai pertanyaan awal Anda, jawabannya tetaplah tidak ada cara secara umum untuk mengetahui panjang sebuah iterator dengan Python.
Mengingat pertanyaan Anda dimotivasi oleh aplikasi pysam library, saya dapat memberikan jawaban yang lebih spesifik: Saya seorang kontributor PySAM dan jawaban definitifnya adalah bahwa file SAM / BAM tidak memberikan jumlah persis pembacaan yang selaras. Informasi ini juga tidak tersedia dengan mudah dari file indeks BAM. Yang terbaik yang bisa dilakukan adalah memperkirakan perkiraan jumlah perataan dengan menggunakan lokasi penunjuk file setelah membaca sejumlah perataan dan mengekstrapolasi berdasarkan ukuran total file. Ini cukup untuk menerapkan bilah kemajuan, tetapi bukan metode penghitungan penyelarasan dalam waktu yang konstan.
Tolok ukur cepat:
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))
Hasil:
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
Yaitu count_iter_items sederhana adalah cara untuk pergi.
Menyesuaikan ini untuk 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)
Ada dua cara untuk mendapatkan panjang "sesuatu" di komputer.
Cara pertama adalah menyimpan hitungan - ini memerlukan apa pun yang menyentuh file / data untuk memodifikasinya (atau kelas yang hanya mengekspos antarmuka - tetapi intinya sama).
Cara lain adalah mengulanginya dan menghitung seberapa besar ukurannya.
Ini bertentangan dengan definisi iterator, yang merupakan penunjuk ke objek, ditambah informasi tentang cara menuju ke objek berikutnya.
Sebuah iterator tidak tahu berapa kali lagi iterator dapat melakukan iterasi hingga dihentikan. Ini bisa jadi tidak terbatas, jadi ketidakterbatasan mungkin jawaban Anda.
Meskipun secara umum tidak mungkin untuk melakukan apa yang diminta, sering kali berguna untuk menghitung berapa banyak item yang diiterasi setelah diiterasi. Untuk itu, Anda bisa menggunakan jaraco.itertools.Counter atau sejenisnya. Berikut adalah contoh menggunakan Python 3 dan rwt untuk memuat paket.
$ 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
Agaknya, Anda ingin menghitung jumlah item tanpa melakukan iterasi, sehingga iterator tidak habis, dan Anda menggunakannya lagi nanti. Ini dimungkinkan dengan copy
ataudeepcopy
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")
Outputnya adalah " Finding the length did not exhaust the iterator!
"
Secara opsional (dan tanpa pertimbangan), Anda bisa membayangi len
fungsi bawaan sebagai berikut:
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
map
iterator yang mengharapkan panggilan fungsi yang dihasilkan hanya terjadi sekali.