ซับเดียวเพื่อตรวจสอบว่าตัววนซ้ำให้องค์ประกอบอย่างน้อยหนึ่งองค์ประกอบหรือไม่?


105

ตอนนี้ฉันกำลังทำสิ่งนี้:

try:
    something = iterator.next()
    # ...
except StopIteration:
    # ...

แต่ฉันต้องการนิพจน์ที่ฉันสามารถวางไว้ในifประโยคง่ายๆ มีอะไรในตัวที่จะทำให้โค้ดนี้ดูเงอะงะน้อยลงหรือไม่?

any()จะส่งคืนFalseหากสามารถทำซ้ำได้ว่างเปล่า แต่อาจทำซ้ำได้ทุกรายการหากไม่มี ฉันต้องการเพียงแค่ตรวจสอบรายการแรกเท่านั้น


มีคนถามว่าฉันพยายามทำอะไร ฉันได้เขียนฟังก์ชันที่เรียกใช้แบบสอบถาม SQL และให้ผลลัพธ์ บางครั้งเมื่อฉันเรียกใช้ฟังก์ชันนี้ฉันแค่อยากรู้ว่าแบบสอบถามส่งคืนอะไรหรือไม่และตัดสินใจตามนั้น


2
นอกจากนี้ปัญหาเกี่ยวกับรหัสนั้นคือคุณไม่สามารถจัดแพคเกจลงในฟังก์ชันได้เนื่องจากจะกินองค์ประกอบแรก คำถามที่ดี.
andrewrk

2
ในกรณีของฉันฉันไม่ต้องการองค์ประกอบเลยฉันแค่อยากรู้ว่ามีอย่างน้อยหนึ่งองค์ประกอบ
Bastien Léonard

2
ห๊ะ! กรณีการใช้งานเดียวกันของฉันในการพยายามหาวิธีแก้ปัญหาเดียวกัน!
Daniel

ที่เกี่ยวข้อง: stackoverflow.com/q/661603/281545
Mr_and_Mrs_D

1
ที่เกี่ยวข้อง: hasNext ใน Python iterators?
user2314737

คำตอบ:


137

anyจะไม่เกินองค์ประกอบแรกถ้าเป็น True ในกรณีที่ตัววนซ้ำให้สิ่งที่เป็นเท็จคุณสามารถเขียนany(True for _ in iterator)ได้


ดูเหมือนว่าจะได้ผลสำหรับฉันด้วยตัวหา re.finditer คุณสามารถทดสอบว่าการหยุดใด ๆ เมื่อประสบความสำเร็จครั้งแรกได้อย่างง่ายดาย: วิ่งany((x > 100 for x in xrange(10000000)))แล้ววิ่งany((x > 10000000 for x in xrange(100000000)))ครั้งที่สองควรใช้เวลานานกว่ามาก
chbrown

1
วิธีนี้ใช้ได้กับกรณีของ "อย่างน้อย x"sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler

11
ในทำนองเดียวกันหากคุณต้องการตรวจสอบว่าตัววนซ้ำว่างหรือไม่ก็สามารถใช้all(False for _ in iterator)เพื่อตรวจสอบว่าตัววนซ้ำว่างหรือไม่ (ทั้งหมดจะคืนค่า True หากตัววนซ้ำว่างเปล่ามิฉะนั้นจะหยุดเมื่อเห็นองค์ประกอบ False แรก)
KGardevoir

24
ปัญหาใหญ่ในการแก้ปัญหานี้คือคุณไม่สามารถใช้ค่าที่ส่งคืนจาก iterator ได้หากไม่ว่างเปล่าใช่ไหม?
Ken Williams

42

ใน Python 2.6+ หากชื่อsentinelถูกผูกไว้กับค่าที่ตัววนซ้ำไม่สามารถให้ผลได้

if next(iterator, sentinel) is sentinel:
    print('iterator was empty')

หากคุณไม่รู้ว่าตัววนซ้ำอาจให้ผลตอบแทนอะไรให้สร้างยามของคุณเอง (เช่นที่ด้านบนของโมดูลของคุณ) ด้วย

sentinel = object()

มิฉะนั้นคุณสามารถใช้ในบทบาทแมวมองค่าใด ๆ ที่คุณ "รู้" (ตามข้อพิจารณาของแอปพลิเคชัน) ที่ตัววนซ้ำไม่สามารถให้ผลได้


1
ดี! สำหรับกรณีการใช้งานของฉันif not next(iterator, None):เพียงพอแล้วเพราะฉันแน่ใจว่าไม่มีจะไม่เป็นส่วนหนึ่งของรายการ ขอบคุณที่ชี้ทางที่ถูกต้อง!
wasabigeek

1
@wasabi โปรดจำไว้ว่าnotจะคืนค่า True สำหรับวัตถุที่เป็นเท็จเช่นรายการว่างเท็จและศูนย์ is not Noneปลอดภัยกว่าและในความคิดของฉันชัดเจนกว่า
Caagr98

21

สิ่งนี้ไม่ได้สะอาดกว่า แต่แสดงวิธีการจัดแพ็กเกจในฟังก์ชันแบบสูญเสีย

def has_elements(iter):
  from itertools import tee
  iter, any_check = tee(iter)
  try:
    any_check.next()
    return True, iter
  except StopIteration:
    return False, iter

has_el, iter = has_elements(iter)
if has_el:
  # not empty

นี่ไม่ใช่ pythonic จริงๆและในบางกรณีอาจมีโซลูชันที่ดีกว่า (แต่ทั่วไปน้อยกว่า) เช่นค่าเริ่มต้นถัดไป

first = next(iter, None)
if first:
  # Do something

นี่ไม่ใช่เรื่องทั่วไปเนื่องจากไม่มีองค์ประกอบที่ถูกต้องในการทำซ้ำหลายรายการ


นี่อาจเป็นวิธีที่ดีที่สุดในการทำเช่นนี้ อย่างไรก็ตามจะช่วยให้ทราบว่า OP กำลังพยายามทำอะไรอยู่? อาจมีวิธีแก้ปัญหาที่หรูหรากว่านี้ (นี่คือ Python หลังจากทั้งหมด)
rossipedia

next()ขอบคุณฉันคิดว่าฉันจะใช้
Bastien Léonard

1
@ บาสเตียนสบายดี แต่ทำเช่นนั้นด้วยทหารรักษาการณ์ที่เหมาะสม(ดูคำตอบของฉัน)
Alex Martelli

3
โซลูชันนี้มีหน่วยความจำรั่วไหลอย่างมาก teeใน itertools จะต้องให้ทุกองค์ประกอบเดียวจาก iterator เดิมในกรณีany_checkที่เคยต้องการที่จะล่วงหน้า สิ่งนี้แย่กว่าการแปลงตัววนซ้ำเดิมเป็นรายการ
Rafał Dowgird

1
@ RafałDowgird นี่แย่กว่าแค่การแปลงตัววนซ้ำเดิมเป็นรายการ ไม่จริง - คิดถึงลำดับที่ไม่มีที่สิ้นสุด
Piotr Dobrogost

6

คุณสามารถใช้ได้:

if zip([None], iterator):
    # ...
else:
    # ...

แต่มันไม่มีอะไรจำเป็นสำหรับโปรแกรมอ่านโค้ด


2
.. (คุณสามารถใช้ 1 รายการที่ทำซ้ำได้แทน [ไม่มี])
mykhal

5

วิธีที่ดีที่สุดที่จะทำคือมีจากpeekablemore_itertools

from more_itertools import peekable
iterator = peekable(iterator)
if iterator:
    # Iterator is non-empty.
else:
    # Iterator is empty.

เพียงระวังหากคุณยังคงอ้างถึงตัวทำซ้ำแบบเก่าตัววนซ้ำจะได้รับขั้นสูง คุณต้องใช้ตัวทำซ้ำที่มองเห็นได้ใหม่นับจากนั้นเป็นต้นมา จริงๆแล้ว peekable คาดว่าจะเป็นโค้ดเพียงบิตเดียวที่แก้ไขตัววนซ้ำแบบเก่าดังนั้นคุณไม่ควรอ้างอิงถึงตัววนซ้ำแบบเก่า


3

สิ่งที่เกี่ยวกับ:

In [1]: i=iter([])

In [2]: bool(next(i,False))
Out[2]: False

In [3]: i=iter([1])

In [4]: bool(next(i,False))
Out[4]: True

4
ที่น่าสนใจ! แต่จะเกิดอะไรขึ้นถ้า next () ส่งคืน False เพราะเป็นสิ่งที่ให้ผลจริง?
Bastien Léonard

@ BastienLéonardสร้างชั้นเรียนclass NotSet: passแล้วตรวจสอบif next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas Dapšauskas

-1

__length_hint__ ประมาณความยาวlist(it)- เป็นวิธีส่วนตัวแม้ว่า:

x = iter( (1, 2, 3) )
help(x.__length_hint__)
      1 Help on built-in function __length_hint__:
      2 
      3 __length_hint__(...)
      4     Private method returning an estimate of len(list(it)).

4
ไม่รับประกันการทำซ้ำทุกครั้ง >>> def it (): ... yield 1 ... yield 2 ... yield 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (โทรล่าสุดล่าสุด): ไฟล์ " <stdin> "บรรทัด 1 ใน <module> AttributeError: วัตถุ 'generator' ไม่มีแอตทริบิวต์ ' length_hint '
andrewrk

3
นอกจากนี้ยังอาจถูกกฎหมายที่จะส่งคืน 0 สำหรับตัววนซ้ำที่มีรายการมากกว่าศูนย์เนื่องจากเป็นเพียงคำใบ้
Glenn Maynard

-1

นี่คือกระดาษห่อตัววนซ้ำแบบ overkill ซึ่งโดยทั่วไปจะช่วยให้ตรวจสอบว่ามีรายการถัดไปหรือไม่ (ผ่านการแปลงเป็นบูลีน) ไม่มีประสิทธิภาพแน่นอน

class LookaheadIterator ():

    def __init__(self, iterator):
        self.__iterator = iterator
        try:
            self.__next      = next (iterator)
            self.__have_next = True
        except StopIteration:
            self.__have_next = False

    def __iter__(self):
        return self

    def next (self):
        if self.__have_next:
            result = self.__next
            try:
                self.__next      = next (self.__iterator)
                self.__have_next = True
            except StopIteration:
                self.__have_next = False

            return result

        else:
            raise StopIteration

    def __nonzero__(self):
        return self.__have_next

x = LookaheadIterator (iter ([]))
print bool (x)
print list (x)

x = LookaheadIterator (iter ([1, 2, 3]))
print bool (x)
print list (x)

เอาท์พุต:

False
[]
True
[1, 2, 3]

-2

สายไปหน่อย แต่ ... คุณสามารถเปลี่ยน iterator เป็นรายการจากนั้นทำงานกับรายการนั้น:

# Create a list of objects but runs out the iterator.
l = [_ for _ in iterator]

# If the list is not empty then the iterator had elements; else it was empty.
if l :
    pass # Use the elements of the list (i.e. from the iterator)
else :
    pass # Iterator was empty, thus list is empty.

4
สิ่งนี้ไม่มีประสิทธิภาพเนื่องจากระบุรายการทั้งหมด จะไม่ทำงานสำหรับเครื่องกำเนิดไฟฟ้าที่ไม่มีที่สิ้นสุด
becko

@becko: เห็นด้วย แต่ดูเหมือนจะไม่เป็นเช่นนั้นในคำถามเดิม
Jens

3
ปัญหาอีกประการหนึ่งคือตัววนซ้ำสามารถสร้างวัตถุจำนวนไม่ จำกัด ซึ่งอาจส่งผลให้หน่วยความจำล้นและความจริงที่ว่าโปรแกรมจะไม่มีวันไปถึงคำสั่งถัดไป
Willem Van Onsem
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.