มีวิธีที่มีประสิทธิภาพในการทราบจำนวนองค์ประกอบที่อยู่ในตัววนซ้ำใน Python โดยทั่วไปโดยไม่ต้องวนซ้ำในแต่ละรายการและนับ
มีวิธีที่มีประสิทธิภาพในการทราบจำนวนองค์ประกอบที่อยู่ในตัววนซ้ำใน Python โดยทั่วไปโดยไม่ต้องวนซ้ำในแต่ละรายการและนับ
คำตอบ:
ไม่เป็นไปไม่ได้
ตัวอย่าง:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
iterator
ไม่ทราบความยาวของจนกว่าคุณจะวนซ้ำ
def gen(): yield random.randint(0, 1)
คือไม่มีที่สิ้นสุดดังนั้นคุณจะไม่สามารถหาความยาวได้โดยการวนซ้ำ
numIters = 0 ; while iterator: numIters +=1
อย่างไร
รหัสนี้ควรใช้งานได้:
>>> 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]
นอกจากนี้ทราบว่าiterator จะหมดโดยการทำเช่นนี้และความพยายามที่จะใช้มันจะเห็นองค์ประกอบไม่มี นั่นเป็นผลที่หลีกเลี่ยงไม่ได้จากการออกแบบตัววนซ้ำ Python หากคุณต้องการเก็บองค์ประกอบไว้คุณจะต้องเก็บไว้ในรายการหรือบางอย่าง
_
อ้างอิงถึง Perl $_
หรือไม่? :)
_
สำหรับตัวแปรดัมมี่ที่คุณไม่สนใจ
ไม่วิธีการใด ๆ ที่จะทำให้คุณต้องแก้ไขทุกผลลัพธ์ คุณทำได้
iter_length = len(list(iterable))
แต่การรันบนตัววนซ้ำแบบไม่มีที่สิ้นสุดจะไม่มีวันกลับมาแน่นอน นอกจากนี้ยังจะใช้ตัววนซ้ำและจะต้องรีเซ็ตหากคุณต้องการใช้เนื้อหา
การบอกเราว่าคุณกำลังพยายามแก้ไขปัญหาที่แท้จริงอะไรบ้างอาจช่วยให้เราพบวิธีที่ดีกว่าในการบรรลุเป้าหมายที่แท้จริงของคุณ
แก้ไข: การใช้list()
จะอ่านซ้ำทั้งหมดลงในหน่วยความจำในครั้งเดียวซึ่งอาจไม่เป็นที่ต้องการ อีกวิธีหนึ่งคือการทำ
sum(1 for _ in iterable)
ตามที่บุคคลอื่นโพสต์ นั่นจะหลีกเลี่ยงไม่ให้มันอยู่ในความทรงจำ
len(list(iterable))
มันจะโหลดข้อมูลทั้งหมดไปยังหน่วยความจำ คุณสามารถใช้: reduce(lambda x, _: x+1, iterable, 0)
. แก้ไข: รหัส Zonda333 พร้อมผลรวมก็ดีเช่นกัน
functools.reduce
คุณทำไม่ได้ (ยกเว้นประเภทของตัววนซ้ำเฉพาะที่ใช้วิธีการเฉพาะบางอย่างที่ทำให้เป็นไปได้)
โดยทั่วไปคุณสามารถนับรายการวนซ้ำได้โดยการใช้ตัววนซ้ำเท่านั้น หนึ่งในวิธีที่มีประสิทธิภาพมากที่สุด:
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 3.x แทนที่itertools.izip
ด้วยzip
)
sum(1 for _ in iterator)
นี้เร็วกว่าเกือบสองเท่า
zip
สำคัญ : ถ้าคุณผ่านzip(counter, iterable)
จริงคุณจะได้รับ 1 มากกว่าจำนวนที่ซ้ำได้!
ใจดี. คุณสามารถตรวจสอบ__length_hint__
วิธีการได้ แต่ขอเตือนว่า (อย่างน้อยก็ถึง Python 3.4 ตามที่ gsnedders ชี้ให้เห็นอย่างเป็นประโยชน์) เป็นรายละเอียดการใช้งานที่ไม่มีเอกสาร ( ข้อความต่อไปนี้ในเธรด ) ซึ่งอาจหายไปหรือเรียกปีศาจจมูกแทนได้
มิฉะนั้นไม่ ตัวทำซ้ำเป็นเพียงวัตถุที่เปิดเผยnext()
เมธอดเท่านั้น StopIteration
คุณสามารถเรียกมันหลายครั้งตามที่จำเป็นและพวกเขาอาจจะหรืออาจจะไม่ได้ในที่สุดก็ยก โชคดีที่พฤติกรรมนี้ส่วนใหญ่โปร่งใสสำหรับผู้เขียนโค้ด :)
ดังนั้นสำหรับผู้ที่ต้องการทราบข้อมูลสรุปของการสนทนานั้น คะแนนสูงสุดสุดท้ายสำหรับการนับนิพจน์เครื่องกำเนิดไฟฟ้าที่มีความยาว 50 ล้านโดยใช้:
len(list(gen))
, len([_ for _ in gen])
, sum(1 for _ in gen),
ilen(gen)
(จากmore_itertool ),reduce(lambda c, i: c + 1, gen, 0)
, จัดเรียงตามประสิทธิภาพของการดำเนินการ (รวมถึงการใช้หน่วยความจำ) จะทำให้คุณประหลาดใจ:
``
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('รายการวินาที', 1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr วินาที', 2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('ผลรวมวินาที', 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, วินาที', 9.812256851990242)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('ลดวินาที', 13.436614598002052) "
ดังนั้นจึงlen(list(gen))
เป็นหน่วยความจำที่ใช้บ่อยที่สุดและใช้งานน้อยที่สุด
len(list(gen))
ควรใช้หน่วยความจำน้อยกว่าวิธีการลด อดีตสร้างใหม่list
ที่เกี่ยวข้องกับการจัดสรรหน่วยความจำในขณะที่หลังไม่ควร ดังนั้นฉันคาดหวังว่าหน่วยความจำรุ่นหลังจะมีประสิทธิภาพมากขึ้น นอกจากนี้การใช้หน่วยความจำจะขึ้นอยู่กับประเภทองค์ประกอบ
len(tuple(iterable))
ได้อย่างมีประสิทธิภาพมากขึ้น: บทความโดย Nelson Minar
ฉันชอบแพ็คเกจcardinalityสำหรับสิ่งนี้มันมีน้ำหนักเบามากและพยายามใช้การใช้งานที่เร็วที่สุดเท่าที่จะเป็นไปได้ขึ้นอยู่กับการทำซ้ำ
การใช้งาน:
>>> 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
ตัววนซ้ำเป็นเพียงวัตถุที่มีตัวชี้ไปยังวัตถุถัดไปที่จะอ่านโดยบัฟเฟอร์หรือสตรีมบางประเภทมันเหมือนกับ LinkedList ที่คุณไม่รู้ว่าคุณมีกี่สิ่งจนกว่าคุณจะวนซ้ำผ่านสิ่งเหล่านั้น ตัวทำซ้ำมีจุดมุ่งหมายเพื่อให้มีประสิทธิภาพเพราะสิ่งที่พวกเขาทำคือบอกคุณว่าจะมีอะไรต่อไปโดยการอ้างอิงแทนที่จะใช้การจัดทำดัชนี (แต่อย่างที่คุณเห็นคุณสูญเสียความสามารถในการดูจำนวนรายการถัดไป)
สำหรับคำถามเดิมของคุณคำตอบก็คือโดยทั่วไปไม่มีวิธีใดที่จะทราบความยาวของตัววนซ้ำใน Python
เนื่องจากคำถามของคุณได้รับแรงบันดาลใจจากแอปพลิเคชันไลบรารี pysam ฉันสามารถให้คำตอบที่เฉพาะเจาะจงมากขึ้น: ฉันเป็นผู้สนับสนุน PySAM และคำตอบที่ชัดเจนคือไฟล์ SAM / BAM ไม่ได้ระบุจำนวนการอ่านที่สอดคล้องกัน ข้อมูลนี้ไม่สามารถหาได้ง่ายจากไฟล์ดัชนี BAM วิธีที่ดีที่สุดคือการประมาณจำนวนการจัดแนวโดยประมาณโดยใช้ตำแหน่งของตัวชี้ไฟล์หลังจากอ่านจำนวนการจัดแนวและการคาดคะเนตามขนาดทั้งหมดของไฟล์ นี่เพียงพอที่จะใช้แถบความคืบหน้า แต่ไม่ใช่วิธีการนับการจัดตำแหน่งในเวลาคงที่
เกณฑ์มาตรฐานอย่างรวดเร็ว:
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)
มีสองวิธีในการรับความยาวของ "บางสิ่ง" บนคอมพิวเตอร์
วิธีแรกคือการจัดเก็บการนับ - สิ่งนี้ต้องการสิ่งใดก็ตามที่สัมผัสกับไฟล์ / ข้อมูลเพื่อแก้ไข (หรือคลาสที่เปิดเผยเฉพาะอินเทอร์เฟซ - แต่มันกลับมาเหมือนเดิม)
อีกวิธีหนึ่งคือการทำซ้ำและนับว่ามันใหญ่แค่ไหน
เป็นเรื่องปกติที่จะใส่ข้อมูลประเภทนี้ในส่วนหัวของไฟล์และสำหรับ pysam จะให้คุณเข้าถึงข้อมูลนี้ได้ ฉันไม่ทราบรูปแบบ แต่คุณได้ตรวจสอบ API แล้วหรือยัง
อย่างที่คนอื่นบอกคุณไม่สามารถทราบความยาวจากตัววนซ้ำได้
สิ่งนี้ขัดกับคำจำกัดความของตัววนซ้ำซึ่งเป็นตัวชี้ไปยังวัตถุรวมทั้งข้อมูลเกี่ยวกับวิธีไปยังวัตถุถัดไป
ตัววนซ้ำไม่รู้ว่าจะสามารถทำซ้ำได้อีกกี่ครั้งจนกว่าจะยุติ สิ่งนี้อาจไม่มีที่สิ้นสุดดังนั้นความไม่มีที่สิ้นสุดอาจเป็นคำตอบของคุณ
แม้ว่าโดยทั่วไปจะเป็นไปไม่ได้ที่จะทำตามที่ถูกถาม แต่ก็ยังมีประโยชน์ที่จะนับจำนวนรายการที่ทำซ้ำหลังจากทำซ้ำแล้วซ้ำอีก สำหรับสิ่งนั้นคุณสามารถใช้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
def count_iter(iter):
sum = 0
for _ in iter: sum += 1
return sum
สันนิษฐานว่าคุณต้องการนับจำนวนรายการโดยไม่ต้องวนซ้ำเพื่อไม่ให้ตัววนซ้ำหมดและคุณจะใช้อีกครั้งในภายหลัง เป็นไปได้ด้วย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
map
วนซ้ำโดยคาดว่าการเรียกฟังก์ชันผลลัพธ์จะเกิดขึ้นเพียงครั้งเดียว