ค้นหาองค์ประกอบแรกในลำดับที่ตรงกับเพรดิเคต


171

ฉันต้องการวิธีใช้สำนวนเพื่อค้นหาองค์ประกอบแรกในรายการที่ตรงกับภาคแสดง

รหัสปัจจุบันค่อนข้างน่าเกลียด:

[x for x in seq if predicate(x)][0]

ฉันคิดว่าจะเปลี่ยนเป็น:

from itertools import dropwhile
dropwhile(lambda x: not predicate(x), seq).next()

แต่จะต้องมีสิ่งที่สวยงามกว่านี้ ... และมันจะดีถ้ามันคืนNoneค่าแทนที่จะยกข้อยกเว้นหากไม่พบคู่ที่ตรงกัน

ฉันรู้ว่าฉันสามารถกำหนดฟังก์ชันเช่น:

def get_first(predicate, seq):
    for i in seq:
        if predicate(i): return i
    return None

แต่ค่อนข้างจืดชืดที่จะเริ่มต้นกรอกรหัสด้วยฟังก์ชั่นยูทิลิตี้เช่นนี้ (และคนอาจจะไม่สังเกตเห็นว่าพวกเขามีอยู่แล้วดังนั้นพวกเขาจึงมักจะทำซ้ำในช่วงเวลา) หากมีการสร้างอินที่ให้ไว้เหมือนกัน


3
นอกเหนือจากการถูกถามในภายหลังว่า " ลำดับฟังก์ชั่นการค้นหาหลาม " คำถามนี้มีชื่อที่ดีกว่ามาก
Wolf

คำตอบ:


250

ในการค้นหาองค์ประกอบแรกในลำดับseqที่ตรงกับpredicate:

next(x for x in seq if predicate(x))

หรือ( itertools.ifilterบน Python 2) :

next(filter(predicate, seq))

มันจะเพิ่มขึ้นStopIterationหากไม่มี


หากต้องการส่งคืนNoneหากไม่มีองค์ประกอบดังกล่าว:

next((x for x in seq if predicate(x)), None)

หรือ:

next(filter(predicate, seq), None)

28
หรือคุณสามารถระบุอาร์กิวเมนต์ "ค่าเริ่มต้น" ตัวที่สองให้กับnextที่ใช้แทนการเพิ่มข้อยกเว้น
Karl Knechtel

2
@fortran: next()มีให้ตั้งแต่ Python 2.6 คุณสามารถอ่านมีอะไรใหม่ในหน้าเพื่อทำความคุ้นเคยกับคุณสมบัติใหม่ได้อย่างรวดเร็ว
jfs

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

2
@geekazoid: seq.find(&method(:predicate))หรือกระชับยิ่งขึ้นสำหรับวิธีการอินสแตนซ์เช่น:[1,1,4].find(&:even?)
jfs

16
ifilterถูกเปลี่ยนชื่อเป็นfilterใน Python 3
tsauerwein

92

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

next((x for x in seq if predicate(x)), None)

แม้ว่าสำหรับสายการบินเดียวคุณต้องใช้ Python> = 2.6

บทความยอดนิยมนี้ค่อนข้างพูดถึงปัญหานี้เพิ่มเติม: ฟังก์ชั่น Clean-Python ค้นหาในรายการ? .


8

ฉันไม่คิดว่าจะมีอะไรผิดปกติกับวิธีแก้ไขปัญหาที่คุณเสนอในคำถามของคุณ

ในรหัสของฉันฉันจะใช้มันเช่นนี้แม้ว่า:

(x for x in seq if predicate(x)).next()

ไวยากรณ์กับ()สร้างตัวสร้างซึ่งมีประสิทธิภาพมากกว่าการสร้างรายการทั้งหมดพร้อม[]กัน


และไม่เพียงแค่นั้น - กับ[]คุณอาจพบปัญหาหากตัววนซ้ำไม่สิ้นสุดหรือองค์ประกอบของมันยากที่จะสร้างขึ้นมาในภายหลังจะได้รับ ...
glglgl

6
'generator' object has no attribute 'next'ใน Python 3
jfs

@glglgl - สำหรับจุดแรก (ไม่สิ้นสุด) ฉันสงสัยว่าเป็นอาร์กิวเมนต์เป็นลำดับที่ จำกัด [แม่นยำมากขึ้นรายการตามคำถาม OP '] สำหรับวินาที: อีกครั้งเนื่องจากอาร์กิวเมนต์ที่ให้มาเป็นลำดับวัตถุควรได้รับการสร้างและจัดเก็บตามเวลาที่ฟังก์ชั่นนี้เรียกว่า .... หรือฉันขาดอะไรไปหรือเปล่า?
mac

@JFSebastian - ขอบคุณฉันไม่รู้เหมือนกัน! :) จากความอยากรู้หลักการออกแบบที่อยู่เบื้องหลังตัวเลือกนี้คืออะไร
mac

@mac - เพื่อความสอดคล้องกับขีดล่างคู่ของวิธีพิเศษอื่น ๆ ดูpython.org/dev/peps/pep-3114
Chewie

1

คำตอบของเจเอฟเจบาสเตียนนั้นสง่างามที่สุด แต่ต้องการงูใหญ่ 2.6 ตามที่ Fortran ชี้ให้เห็น

สำหรับ Python เวอร์ชัน <2.6 นี่คือสิ่งที่ดีที่สุดที่ฉันจะได้รับ:

from itertools import repeat,ifilter,chain
chain(ifilter(predicate,seq),repeat(None)).next()

อีกทางเลือกหนึ่งถ้าคุณต้องการรายการในภายหลัง (รายการจัดการ StopIteration) หรือคุณต้องการมากกว่าแค่รายการแรก แต่ก็ยังไม่ครบทั้งหมดคุณสามารถทำได้ด้วย islice:

from itertools import islice,ifilter
list(islice(ifilter(predicate,seq),1))

UPDATE: แม้ว่าฉันจะใช้ฟังก์ชั่นที่กำหนดไว้ล่วงหน้าที่เรียกว่า first () เป็นการส่วนตัวที่จับ StopIteration และคืนค่า None นี่คือการปรับปรุงที่เป็นไปได้สำหรับตัวอย่างข้างต้น: หลีกเลี่ยงการใช้ filter / ifilter:

from itertools import islice,chain
chain((x for x in seq if predicate(x)),repeat(None)).next()

11
อ๊ะ! ถ้าพูดถึงเรื่องนั้นฉันก็จะทำ "ห่วง" แบบง่าย ๆ ด้วย "ถ้า" ข้างใน - อ่านได้ง่ายขึ้นมาก
Nick Perkins
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.