Tìm phần tử đầu tiên trong chuỗi khớp với một vị ngữ


171

Tôi muốn một cách thành ngữ để tìm phần tử đầu tiên trong danh sách khớp với một vị ngữ.

Mã hiện tại khá xấu:

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

Tôi đã nghĩ về việc thay đổi nó thành:

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

Nhưng phải có một cái gì đó thanh lịch hơn ... Và sẽ thật tuyệt nếu nó trả về một Nonegiá trị thay vì đưa ra một ngoại lệ nếu không tìm thấy kết quả khớp nào.

Tôi biết tôi chỉ có thể định nghĩa một chức năng như:

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

Nhưng thật vô vị khi bắt đầu điền mã với các hàm tiện ích như thế này (và mọi người có thể sẽ không nhận thấy rằng chúng đã ở đó, vì vậy chúng có xu hướng được lặp lại theo thời gian) nếu có các phần mềm được xây dựng tương tự.


3
Bên cạnh việc được hỏi muộn hơn " chức năng tìm chuỗi python ", câu hỏi này có tiêu đề tốt hơn nhiều .
Sói

Câu trả lời:


249

Để tìm phần tử đầu tiên trong chuỗi seqkhớp với predicate:

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

Hoặc ( itertools.ifiltertrên Python 2) :

next(filter(predicate, seq))

Nó tăng StopIterationnếu không có.


Để trở về Nonenếu không có yếu tố đó:

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

Hoặc là:

next(filter(predicate, seq), None)

27
Hoặc bạn có thể cung cấp một đối số "mặc định" thứ hai cho nextđiều đó được sử dụng thay vì đưa ra ngoại lệ.
Karl Knechtel

2
@fortran: next()khả dụng kể từ Python 2.6 Bạn có thể đọc trang What What mới để nhanh chóng làm quen với các tính năng mới.
jfs

1
Tôi là một người mới chơi python và đọc các tài liệu và ifilter sử dụng phương thức "suất". Tôi cho rằng điều này có nghĩa là vị ngữ được đánh giá một cách lười biếng khi chúng ta đi. tức là, chúng tôi không chạy biến vị ngữ qua toàn bộ danh sách vì tôi có một hàm vị ngữ hơi đắt và tôi chỉ muốn lặp lại cho đến khi chúng tôi tìm thấy một mục
Kannan Ekanath

2
@geekazoid: seq.find(&method(:predicate))hoặc thậm chí ngắn gọn hơn cho các phương thức ví dụ:[1,1,4].find(&:even?)
jfs

16
ifilterđã được đổi tên thành filterPython 3.
tsauerwein

92

Bạn có thể sử dụng biểu thức trình tạo với giá trị mặc định và sau nextđó:

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

Mặc dù đối với lớp lót này, bạn cần sử dụng Python> = 2.6.

Bài viết khá phổ biến này thảo luận thêm về vấn đề này: Chức năng tìm trong danh sách Python sạch nhất? .


8

Tôi không nghĩ có gì sai với bất kỳ giải pháp nào bạn đề xuất trong câu hỏi của bạn.

Trong mã của riêng tôi, tôi sẽ thực hiện nó như thế này:

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

Cú pháp với việc ()tạo một trình tạo, hiệu quả hơn so với việc tạo tất cả danh sách cùng một lúc [].


Và không chỉ vậy - với []bạn có thể gặp vấn đề nếu trình lặp không bao giờ kết thúc hoặc các phần tử của nó khó tạo, càng về sau nó càng ...
glglgl

6
'generator' object has no attribute 'next'trên Python 3.
jfs

@glglgl - Đối với điểm đầu tiên (không bao giờ kết thúc) Tôi nghi ngờ điều đó, vì đối số là một chuỗi hữu hạn [chính xác hơn là một danh sách theo câu hỏi của OP]. Đối với lần thứ hai: một lần nữa, vì đối số được cung cấp là một chuỗi, nên các đối tượng đã được tạo và lưu trữ vào thời điểm hàm này được gọi .... hoặc tôi có thiếu thứ gì không?
mac

@JFSebastian - Cảm ơn bạn, tôi đã không biết điều đó! :) Vì tò mò, nguyên tắc thiết kế đằng sau sự lựa chọn này là gì?
mac

@mac - Để thống nhất với dấu gạch dưới kép của các phương thức đặc biệt khác. Xem python.org/dev/peps/pep-3114
Chewie

1

Câu trả lời của JF Sebastian là thanh lịch nhất nhưng đòi hỏi python 2.6 như fortran đã chỉ ra.

Đối với phiên bản Python <2.6, đây là phiên bản tốt nhất tôi có thể nghĩ ra:

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

Ngoài ra, nếu bạn cần một danh sách sau này (danh sách xử lý StopIteration) hoặc bạn cần nhiều hơn chỉ là đầu tiên nhưng vẫn không phải là tất cả, bạn có thể thực hiện với islice:

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

CẬP NHẬT: Mặc dù cá nhân tôi đang sử dụng một hàm được xác định trước có tên là First () để bắt StopIteration và trả về Không, đây là một cải tiến có thể có trong ví dụ trên: tránh sử dụng bộ lọc / ifilter:

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

11
Rất tiếc! nếu điều đó xảy ra, tôi sẽ chỉ thực hiện vòng lặp "for" đơn giản với một chữ "nếu" bên trong nó - dễ đọc hơn nhiều
Nick Perkins
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.