รับไอเท็มแรกจาก iterable ที่ตรงกับเงื่อนไข


303

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

def first(the_iterable, condition = lambda x: True):
    for i in the_iterable:
        if condition(i):
            return i

ฟังก์ชั่นนี้สามารถใช้งานได้ดังนี้:

>>> first(range(10))
0
>>> first(range(10), lambda i: i > 3)
4

อย่างไรก็ตามฉันไม่สามารถคิดถึงการใช้งานในตัว / ซับในที่ดีเพื่อให้ฉันทำสิ่งนี้ได้ ฉันไม่ต้องการคัดลอกฟังก์ชั่นนี้โดยเฉพาะถ้าไม่จำเป็น มีวิธีการรับไอเท็มแรกที่ตรงกับเงื่อนไขหรือไม่?


คำตอบ:


476

ใน Python 2.6 หรือใหม่กว่า:

หากคุณต้องการStopIterationได้รับการเพิ่มหากไม่พบองค์ประกอบที่ตรงกัน:

next(x for x in the_iterable if x > 3)

หากคุณต้องการที่จะส่งคืนdefault_value(เช่นNone) แทน:

next((x for x in the_iterable if x > 3), default_value)

โปรดทราบว่าคุณต้องการวงเล็บคู่เพิ่มเติมรอบนิพจน์ตัวสร้างในกรณีนี้จำเป็นต้องใช้วงเล็บทุกครั้งที่นิพจน์ตัวสร้างไม่ได้เป็นเพียงอาร์กิวเมนต์เท่านั้น

ฉันเห็นคำตอบส่วนใหญ่อย่างเด็ดขาดไม่สนใจสิ่งที่nextมีอยู่ภายในและดังนั้นฉันจึงคิดว่าด้วยเหตุผลลึกลับบางอย่างพวกเขามุ่งเน้นไปที่รุ่น 2.5 และรุ่นเก่ากว่า 100% - โดยไม่พูดถึงปัญหาของ Python-version (แต่ฉันไม่เห็น คำตอบที่ทำพูดถึงnextในตัวซึ่งเป็นเหตุผลที่ผมคิดว่ามันจำเป็นที่จะต้องให้คำตอบกับตัวเอง - อย่างน้อย "รุ่นที่ถูกต้อง" ปัญหาที่ได้รับการบันทึกไว้ด้วยวิธีนี้ ;-)

ใน 2.5 .next()วิธีการวนซ้ำจะยกStopIterationหากตัววนซ้ำเสร็จสิ้นทันที - เช่นสำหรับกรณีการใช้งานของคุณหากไม่มีรายการใดในตัววนซ้ำที่ตรงตามเงื่อนไข หากคุณไม่สนใจ (เช่นคุณรู้ว่าต้องมีรายการที่น่าพอใจอย่างน้อยหนึ่งรายการ) จากนั้นใช้เพียงแค่.next()(ดีที่สุดสำหรับ genexp บรรทัดสำหรับnextบิวด์อินใน Python 2.6 และดีกว่า)

ถ้าคุณทำดูแลสิ่งห่อในการทำงานตามที่คุณได้ชี้ให้เห็นเป็นครั้งแรกในของคุณ Q ดูเหมือนว่าดีที่สุดและในขณะที่การใช้งานฟังก์ชั่นที่คุณนำเสนอเป็นเพียงแค่ปรับคุณผลัดสามารถใช้itertoolsเป็นfor...: breakห่วงหรือ genexp, หรือtry/except StopIterationในขณะที่ร่างกายของฟังก์ชัน ตามคำแนะนำที่หลากหลาย ไม่มีมูลค่าเพิ่มในตัวเลือกใด ๆ เหล่านี้มากนักดังนั้นฉันจะไปหาเวอร์ชั่นที่เรียบง่ายที่คุณเสนอครั้งแรก


6
ไม่ทำงานตามที่คุณอธิบาย มันจะเพิ่มขึ้นStopIterationเมื่อไม่พบองค์ประกอบ
Suor

เนื่องจากสิ่งนี้เกิดขึ้นในผลการค้นหาฉันได้ติดตามความคิดเห็นของ @ Suor ตั้งแต่ปี 2011 และป้อนข้อความในย่อหน้าแรกอีกเล็กน้อยเพื่อให้ชัดเจนยิ่งขึ้น โปรดไปข้างหน้าและแก้ไขการแก้ไขของฉันหากคุณต้องการ
คอส

4
ตั้งแต่นี้เป็นคำตอบที่เลือกผมรู้สึกถูกบังคับให้แบ่งปันคำตอบถึงการเลือกองค์ประกอบแรกได้อย่างถูกต้องที่นี่ กล่าวโดยย่อ: ไม่ควรส่งเสริมการใช้งานครั้งต่อไป
guyarad

1
@guyarad วิธีการแก้ปัญหาที่เสนอในคำตอบนั้น "ลับ" น้อยกว่าแค่ใช้ถัดไปหรือไม่ อาร์กิวเมนต์เดียวกับคำตอบถัดไป (ในคำตอบนั้น) คือคุณต้องจัดการกับข้อยกเว้น; จริงเหรอ
อับราฮัม TS

มุมมองของฉันแตกต่างจากเวลาที่ฉันเขียนความคิดเห็นเล็กน้อย ฉันเห็นประเด็นของคุณ ที่ถูกกล่าวว่าต้องจัดการStopIterationไม่สวยจริงๆ ใช้วิธีที่ดีกว่า
guyarad

29

เป็นฟังก์ชั่นที่ใช้ซ้ำได้บันทึกและทดสอบ

def first(iterable, condition = lambda x: True):
    """
    Returns the first item in the `iterable` that
    satisfies the `condition`.

    If the condition is not given, returns the first item of
    the iterable.

    Raises `StopIteration` if no item satysfing the condition is found.

    >>> first( (1,2,3), condition=lambda x: x % 2 == 0)
    2
    >>> first(range(3, 100))
    3
    >>> first( () )
    Traceback (most recent call last):
    ...
    StopIteration
    """

    return next(x for x in iterable if condition(x))

เวอร์ชันที่มีอาร์กิวเมนต์เริ่มต้น

@zorf แนะนำรุ่นของฟังก์ชันนี้ซึ่งคุณสามารถมีค่าส่งคืนที่กำหนดไว้ล่วงหน้าหาก iterable ว่างเปล่าหรือไม่มีรายการที่ตรงกับเงื่อนไข:

def first(iterable, default = None, condition = lambda x: True):
    """
    Returns the first item in the `iterable` that
    satisfies the `condition`.

    If the condition is not given, returns the first item of
    the iterable.

    If the `default` argument is given and the iterable is empty,
    or if it has no items matching the condition, the `default` argument
    is returned if it matches the condition.

    The `default` argument being None is the same as it not being given.

    Raises `StopIteration` if no item satisfying the condition is found
    and default is not given or doesn't satisfy the condition.

    >>> first( (1,2,3), condition=lambda x: x % 2 == 0)
    2
    >>> first(range(3, 100))
    3
    >>> first( () )
    Traceback (most recent call last):
    ...
    StopIteration
    >>> first([], default=1)
    1
    >>> first([], default=1, condition=lambda x: x % 2 == 0)
    Traceback (most recent call last):
    ...
    StopIteration
    >>> first([1,3,5], default=1, condition=lambda x: x % 2 == 0)
    Traceback (most recent call last):
    ...
    StopIteration
    """

    try:
        return next(x for x in iterable if condition(x))
    except StopIteration:
        if default is not None and condition(default):
            return default
        else:
            raise

6
หากคุณกำลังห่อด้วยวิธีการอย่างน้อยจับ StopIteration และเพิ่มข้อผิดพลาด EmptySequence คงจะดีกว่านี้มากเมื่อไม่มีองค์ประกอบ
guyarad

@guyarad นั่นคือ ValueError หรือเปล่า?
Caridorc

2
@guyarad StopIterationเป็นข้อยกเว้น "นอกองค์ประกอบ" ซึ่งเป็นที่ยอมรับใน python ฉันไม่เห็นปัญหากับการโยน ฉันอาจจะใช้ค่าเริ่มต้นของ "ไม่มี" ซึ่งสามารถส่งผ่านเป็นพารามิเตอร์เริ่มต้นให้กับฟังก์ชั่น
Baldrickk

1
Baldrickk ฉันรู้สึกว่านี่ไม่ใช่วิธีการทำซ้ำ คุณจะไม่เรียกชื่อนี้ในการแข่งขันของตัววนซ้ำ แต่ฉันไม่รู้สึกเกี่ยวกับมันมากเกินไป :)
guyarad

1
ควรมีอาร์กิวเมนต์เริ่มต้นที่เป็นทางเลือกและหากไม่ได้ระบุอาร์กิวเมนต์นั้นให้ยกข้อยกเว้นเมื่อไม่มีองค์ประกอบในลำดับที่ตรงตามเงื่อนไข
Zorf

28

ข้อยกเว้นแช่ง!

ฉันรักคำตอบนี้ อย่างไรก็ตามเนื่องจากnext()เพิ่มStopIterationข้อยกเว้นเมื่อไม่มีรายการใด ๆ ฉันจะใช้ตัวอย่างต่อไปนี้เพื่อหลีกเลี่ยงข้อยกเว้น:

a = []
item = next((x for x in a), None)

ตัวอย่างเช่น,

a = []
item = next(x for x in a)

จะยกStopIterationข้อยกเว้น;

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

13

คล้ายกับการใช้ifilterคุณสามารถใช้นิพจน์ตัวสร้าง:

>>> (x for x in xrange(10) if x > 5).next()
6

ในกรณีใดกรณีหนึ่งคุณอาจต้องการที่จะจับStopIterationในกรณีที่ไม่มีองค์ประกอบที่ตรงตามเงื่อนไขของคุณ

เทคนิคการพูดฉันคิดว่าคุณสามารถทำสิ่งนี้:

>>> foo = None
>>> for foo in (x for x in xrange(10) if x > 5): break
... 
>>> foo
6

มันจะหลีกเลี่ยงไม่ต้องทำtry/exceptบล็อก แต่ดูเหมือนว่าคลุมเครือและไม่เหมาะสมกับไวยากรณ์


+1: ไม่คลุมเครือหรือไม่เหมาะสม ทุกสิ่งถือว่าเป็นสิ่งสุดท้ายที่ค่อนข้างสะอาด
S.Lott

6
คนสุดท้ายไม่ได้ทำความสะอาดเลย - for foo in genex: breakเป็นเพียงวิธีการหนึ่งfoo = next(genex)โดยไม่ทำให้การมอบหมายชัดเจนและมีข้อยกเว้นที่จะยกขึ้นหากการดำเนินการไม่สมเหตุสมผล การจบด้วยรหัสความล้มเหลวแทนที่จะเป็นข้อยกเว้นมักเป็นสิ่งที่ไม่ดีใน Python
Mike Graham

13

วิธีที่มีประสิทธิภาพมากที่สุดใน Python 3 เป็นหนึ่งในวิธีต่อไปนี้ (ใช้ตัวอย่างที่คล้ายกัน):

ด้วยสไตล์"ความเข้าใจ" :

next(i for i in range(100000000) if i == 1000)

คำเตือน : นิพจน์ใช้งานได้กับ Python 2 แต่ในตัวอย่างนั้นใช้rangeเพื่อส่งคืนออบเจกต์ที่ทำซ้ำได้ใน Python 3 แทนรายการเช่น Python 2 (ถ้าคุณต้องการสร้าง iterable ใน Python 2 ใช้xrangeแทน)

หมายเหตุว่าหลีกเลี่ยงการแสดงออกที่จะสร้างรายชื่อในการแสดงออกของความเข้าใจที่ว่าจะก่อให้เกิดการสร้างรายการที่มีองค์ประกอบก่อนกรององค์ประกอบทั้งหมดและจะทำให้เกิดการประมวลผลตัวเลือกทั้งหมดแทนการหยุดซ้ำครั้งเดียวnext([i for ...])i == 1000

ด้วยรูปแบบ"ใช้งานได้" :

next(filter(lambda i: i == 1000, range(100000000)))

คำเตือน : วิธีนี้ใช้ไม่ได้ใน Python 2 แม้จะแทนที่rangeด้วยxrangeเนื่องจากจะfilterสร้างรายการแทนตัววนซ้ำ (ไม่มีประสิทธิภาพ) และnextฟังก์ชันจะทำงานกับตัววนซ้ำเท่านั้น

ค่าเริ่มต้น

ดังที่กล่าวไว้ในการตอบกลับอื่น ๆ คุณต้องเพิ่มพารามิเตอร์พิเศษให้กับฟังก์ชันnextหากคุณต้องการหลีกเลี่ยงข้อยกเว้นที่เกิดขึ้นเมื่อเงื่อนไขไม่เป็นจริง

สไตล์"ใช้งานได้" :

next(filter(lambda i: i == 1000, range(100000000)), False)

สไตล์"ความเข้าใจ" :

ด้วยสไตล์นี้คุณต้องล้อมรอบนิพจน์ความเข้าใจด้วย()เพื่อหลีกเลี่ยงSyntaxError: Generator expression must be parenthesized if not sole argument:

next((i for i in range(100000000) if i == 1000), False)


6

itertoolsโมดูลที่มีฟังก์ชั่นกรองสำหรับ iterators องค์ประกอบแรกของตัววนซ้ำตัวกรองสามารถรับได้โดยการเรียกnext()มัน:

from itertools import ifilter

print ifilter((lambda i: i > 3), range(10)).next()

2
นิพจน์ตัวสร้างนั้นง่ายกว่า
Eric O Lebigot

1
( i) filterและ ( i) mapสามารถทำให้เกิดความรู้สึกได้สำหรับกรณีที่มีการใช้ฟังก์ชั่นที่มีอยู่แล้ว แต่ในสถานการณ์เช่นนี้มันสมเหตุสมผลมากกว่าที่จะใช้นิพจน์ตัวสร้าง
Mike Graham

นี่คือคำตอบที่ดีที่สุด comprehensions รายการหลีกเลี่ยงxahlee.info/comp/list_comprehension.html
mit

6

สำหรับ Python เวอร์ชันเก่าที่ไม่มีบิวด์อินต่อไปอยู่:

(x for x in range(10) if x > 3).next()

5

โดยใช้

(index for index, value in enumerate(the_iterable) if condition(value))

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

การแสดงออกที่สมบูรณ์เพื่อใช้คือ

first_index = next(index for index, value in enumerate(the_iterable) if condition(value))

นี่first_indexถือว่าค่าของค่าแรกที่ระบุในการแสดงออกที่กล่าวข้างต้น


4

คำถามนี้มีคำตอบที่ดีอยู่แล้ว ฉันแค่เพิ่มสองเซ็นต์ของฉันเพราะฉันลงจอดที่นี่พยายามหาวิธีแก้ไขปัญหาของตัวเองซึ่งคล้ายกับ OP

หากคุณต้องการค้นหาดัชนีของรายการแรกที่ตรงกับเกณฑ์โดยใช้เครื่องกำเนิดคุณสามารถทำได้:

next(index for index, value in enumerate(iterable) if condition)

ดูเพิ่มเติมที่: stackoverflow.com/questions/1701211/…
dreftymac

0

คุณสามารถใช้argwhereฟังก์ชันใน Numpy ได้ ตัวอย่างเช่น:

i) ค้นหา "l" ตัวแรกใน "helloworld":

import numpy as np
l = list("helloworld") # Create list
i = np.argwhere(np.array(l)=="l") # i = array([[2],[3],[8]])
index_of_first = i.min()

ii) ค้นหาหมายเลขสุ่มแรก> 0.1

import numpy as np
r = np.random.rand(50) # Create random numbers
i = np.argwhere(r>0.1)
index_of_first = i.min()

iii) ค้นหาตัวเลขสุ่มตัวสุดท้าย> 0.1

import numpy as np
r = np.random.rand(50) # Create random numbers
i = np.argwhere(r>0.1)
index_of_last = i.max()

-1

ใน Python 3:

a = (None, False, 0, 1)
assert next(filter(None, a)) == 1

ใน Python 2.6:

a = (None, False, 0, 1)
assert next(iter(filter(None, a))) == 1

แก้ไข: ฉันคิดว่ามันชัดเจน แต่เห็นได้ชัดว่าไม่ใช่: แทนที่จะให้Noneคุณส่งผ่านฟังก์ชัน (หรือ a lambda) ด้วยการตรวจสอบเงื่อนไข:

a = [2,3,4,5,6,7,8]
assert next(filter(lambda x: x%2, a)) == 3

-3

หนึ่งในสายการบิน:

thefirst = [i for i in range(10) if i > 3][0]

หากคุณไม่แน่ใจว่าองค์ประกอบใด ๆ จะถูกต้องตามเกณฑ์ที่คุณควรแนบนี้กับtry/exceptตั้งแต่ที่สามารถยก[0]IndexError


TypeError: วัตถุ 'generator' ไม่สามารถอธิบายได้
Josh Lee

ไม่ดีของฉันควรเป็นรายการความเข้าใจไม่ใช่ตัวกำเนิดคงที่ ... ขอบคุณ! :)
Mizipzor

2
ไม่มีเหตุผลในการประเมินซ้ำทั้งหมด (ซึ่งอาจเป็นไปไม่ได้) มีความแข็งแกร่งและมีประสิทธิภาพมากกว่าในการใช้หนึ่งในโซลูชันอื่น ๆ ที่มีให้
Mike Graham
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.