Nhận mục đầu tiên từ một lần lặp phù hợp với điều kiện


303

Tôi muốn có được mục đầu tiên từ một danh sách phù hợp với một điều kiện. Điều quan trọng là phương pháp kết quả không xử lý toàn bộ danh sách, có thể khá lớn. Ví dụ, chức năng sau là đầy đủ:

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

Hàm này có thể được sử dụng như thế này:

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

Tuy nhiên, tôi không thể nghĩ ra một công cụ tích hợp / một lớp lót tốt để cho tôi làm điều này. Tôi đặc biệt không muốn sao chép chức năng này nếu tôi không phải làm vậy. Có cách tích hợp nào để có được mục đầu tiên phù hợp với điều kiện không?


Câu trả lời:


476

Trong Python 2.6 hoặc mới hơn:

Nếu bạn muốn StopIterationđược nâng lên nếu không tìm thấy yếu tố phù hợp:

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

Nếu bạn muốn default_value(ví dụ None) được trả lại thay thế:

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

Lưu ý rằng bạn cần thêm một cặp dấu ngoặc đơn xung quanh biểu thức trình tạo trong trường hợp này - chúng cần thiết bất cứ khi nào biểu thức trình tạo không phải là đối số duy nhất.

Tôi thấy hầu hết các câu trả lời đều kiên quyết bỏ qua phần tích nexthợp sẵn và vì vậy tôi cho rằng vì một số lý do bí ẩn, họ tập trung 100% vào các phiên bản 2.5 trở lên - mà không đề cập đến vấn đề phiên bản Python (nhưng sau đó tôi không thấy đề cập đến trong câu trả lời đó làm đề cập đến nextbuilt-in, đó là lý do tại sao tôi nghĩ rằng nó cần thiết để cung cấp một câu trả lời bản thân mình - ít nhất là "đúng phiên bản" vấn đề được ghi nhận theo cách này ;-).

Trong 2.5, .next()phương thức của trình vòng lặp ngay lập tức tăng lên StopIterationnếu trình vòng lặp ngay lập tức kết thúc - tức là, đối với trường hợp sử dụng của bạn, nếu không có mục nào trong vòng lặp thỏa mãn điều kiện. Nếu bạn không quan tâm (nghĩa là, bạn biết rằng phải có ít nhất một mục thỏa đáng) thì chỉ cần sử dụng .next()(tốt nhất là trên genEx, dòng dành cho nextPython 2.6 tích hợp và tốt hơn).

Nếu bạn làm cẩn thận, gói thứ trong một chức năng như bạn đã đầu tiên đã nêu trong Q của bạn có vẻ tốt nhất, và trong khi thực hiện chức năng bạn được đề nghị là tốt, bạn có cách khác có thể sử dụng itertools, một for...: breakvòng lặp, hoặc một genexp, hoặc try/except StopIterationlà thân của hàm , như nhiều câu trả lời khác nhau được đề xuất. Không có nhiều giá trị gia tăng trong bất kỳ giải pháp thay thế nào trong số này, vì vậy tôi sẽ chọn phiên bản đơn giản hoàn toàn mà bạn đề xuất đầu tiên.


6
Không hoạt động như bạn mô tả. Nó tăng lên StopIterationkhi không tìm thấy phần tử nào
Suor

Vì điều này xuất hiện trong kết quả tìm kiếm, tôi đã theo dõi nhận xét của @ Suor từ năm 2011 và điều chỉnh lại đoạn đầu tiên một chút để mọi thứ rõ ràng hơn. Hãy tiếp tục và sửa đổi chỉnh sửa của tôi nếu bạn cần.
Kos

4
Vì đây là câu trả lời được chọn, tôi cảm thấy bắt buộc phải chia sẻ câu trả lời để chọn chính xác phần tử đầu tiên ở đây . Tóm lại: việc sử dụng tiếp theo không nên được khuyến khích.
Guyarad

1
@guyarad làm thế nào là giải pháp được đề xuất trong câu trả lời đó ít "khó hiểu" hơn là chỉ sử dụng tiếp theo? Đối số duy nhất chống lại tiếp theo (trong câu trả lời đó) là bạn phải xử lý một ngoại lệ; có thật không ?
Abraham TS

Quan điểm của tôi hơi khác so với lúc tôi viết bình luận. Tôi thấy điểm của bạn. Điều đó đang được nói, phải xử lý StopIterationlà thực sự không đẹp. Sử dụng tốt hơn một phương pháp.
Guyarad

29

Là một chức năng tái sử dụng, tài liệu và thử nghiệm

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))

Phiên bản với đối số mặc định

@zorf đã đề xuất một phiên bản của hàm này trong đó bạn có thể có giá trị trả về được xác định trước nếu iterable trống hoặc không có mục nào khớp với điều kiện:

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
Nếu bạn đang gói nó bằng một phương thức, ít nhất hãy bắt StopIteration và đưa ra lỗi EmptySequence. Sẽ đẹp hơn nhiều khi không có yếu tố.
Guyarad

@guyarad Đó có phải là một loại ValueError không?
Caridorc

2
@guyarad StopIterationlà ngoại lệ "ngoài các yếu tố" chính tắc trong python. Tôi không thấy vấn đề với nó bị ném. Tôi có thể sử dụng mặc định "Không" có thể được truyền vào dưới dạng tham số mặc định cho hàm.
Baldrickk

1
Baldrickk Tôi cảm thấy như đây không phải là một phương pháp lặp. Bạn sẽ không gọi cái này trong cuộc thi của một iterator. Nhưng tôi không cảm thấy quá mạnh mẽ về điều đó :)
Guyarad

1
Cần có một đối số mặc định tùy chọn và nếu không cung cấp đối số đó, thì chỉ đưa ra một ngoại lệ khi không có phần tử nào trong chuỗi thỏa mãn điều kiện.
Zorf

28

Ngoại lệ chết tiệt!

Tôi thích câu trả lời này . Tuy nhiên, vì next()đưa ra một StopIterationngoại lệ khi không có mục nào, tôi sẽ sử dụng đoạn mã sau để tránh ngoại lệ:

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

Ví dụ,

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

Sẽ đưa ra một StopIterationngoại lệ;

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

13

Tương tự như việc sử dụng ifilter, bạn có thể sử dụng biểu thức trình tạo:

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

Trong cả hai trường hợp, bạn có thể muốn bắt StopIteration mặc dù, trong trường hợp không có yếu tố nào thỏa mãn điều kiện của bạn.

Về mặt kỹ thuật, tôi cho rằng bạn có thể làm một cái gì đó như thế này:

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

Nó sẽ tránh phải làm một try/exceptkhối. Nhưng điều đó có vẻ mơ hồ và lạm dụng cú pháp.


+1: Không tối nghĩa, cũng không lạm dụng. Tất cả mọi thứ được xem xét, cuối cùng có vẻ khá sạch sẽ.
S.Lott

6
Cách cuối cùng hoàn toàn không phải for foo in genex: breaklà một trò chơi sạch sẽ chỉ là một cách làm foo = next(genex)mà không làm cho nhiệm vụ rõ ràng và ngoại lệ sẽ được nêu ra nếu hoạt động không có nghĩa là bị nghiền nát. Kết thúc với một mã lỗi thay vì bắt một ngoại lệ thường là một điều xấu trong Python.
Mike Graham

13

Cách hiệu quả nhất trong Python 3 là một trong những cách sau (sử dụng một ví dụ tương tự):

Với phong cách "thấu hiểu" :

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

CẢNH BÁO : Biểu thức cũng hoạt động với Python 2, nhưng trong ví dụ được sử dụng rangesẽ trả về một đối tượng có thể lặp trong Python 3 thay vì danh sách như Python 2 (nếu bạn muốn xây dựng một iterable trong Python 2, hãy sử dụngxrange thay thế).

Lưu ý rằng biểu thức tránh xây dựng danh sách trong biểu thức hiểu next([i for ...]), điều đó sẽ gây ra việc tạo danh sách với tất cả các phần tử trước khi lọc các phần tử và sẽ gây ra xử lý toàn bộ các tùy chọn, thay vì dừng lặp lại một lầni == 1000 .

Với phong cách "chức năng" :

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

CẢNH BÁO : Điều này không hoạt động trong Python 2, thậm chí thay thế rangebằng xrangedo filtertạo ra một danh sách thay vì một trình vòng lặp (không hiệu quả) vànext chức năng chỉ hoạt động với các trình vòng lặp.

Giá trị mặc định

Như đã đề cập trong các phản hồi khác, bạn phải thêm một tham số phụ cho hàm nextnếu bạn muốn tránh một ngoại lệ được nêu ra khi điều kiện không được đáp ứng.

kiểu "chức năng" :

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

phong cách "thấu hiểu" :

Với phong cách này, bạn cần bao quanh biểu thức hiểu ()để tránh SyntaxError: Generator expression must be parenthesized if not sole argument:

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


6

Các itertoolsmô-đun chứa một hàm lọc cho vòng lặp. Phần tử đầu tiên của trình vòng lặp được lọc có thể thu được bằng cách gọi next()nó:

from itertools import ifilter

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

2
Biểu thức máy phát điện đơn giản hơn.
Eric O Lebigot

1
( i) filtervà ( i) mapcó thể có ý nghĩa đối với các trường hợp các hàm đang được áp dụng đã tồn tại, nhưng trong tình huống như thế này, sẽ có ý nghĩa hơn rất nhiều khi chỉ sử dụng biểu thức trình tạo.
Mike Graham

Đây là câu trả lời tốt nhất. Tránh hiểu danh sách xahlee.info/comp/list_comprehension.html
mit

6

Đối với các phiên bản cũ hơn của Python nơi tích hợp tiếp theo không tồn tại:

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

5

Bằng cách sử dụng

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

người ta có thể kiểm tra điều kiện của giá trị của mục đầu tiên trong the_iterable và có được chỉ mục của nó mà không cần phải đánh giá tất cả các mục trong the_iterable .

Biểu thức đầy đủ để sử dụng là

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

Ở đây first_index giả định giá trị của giá trị đầu tiên được xác định trong biểu thức được thảo luận ở trên.


4

Câu hỏi này đã có câu trả lời tuyệt vời. Tôi chỉ thêm hai xu của mình vì tôi đã hạ cánh ở đây cố gắng tìm giải pháp cho vấn đề của riêng tôi, rất giống với OP.

Nếu bạn muốn tìm INDEX của mục đầu tiên phù hợp với tiêu chí bằng cách sử dụng trình tạo, bạn chỉ cần thực hiện:

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


0

Bạn cũng có thể sử dụng argwhere chức năng trong Numpy. Ví dụ:

i) Tìm chữ "l" đầu tiên trong "hellowworld":

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) Tìm số ngẫu nhiên đầu tiên> 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) Tìm số ngẫu nhiên cuối cùng> 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

Trong Python 3:

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

Trong Python 2.6:

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

EDIT: Tôi nghĩ đó là điều hiển nhiên, nhưng rõ ràng là không: thay vì Nonebạn có thể vượt qua một chức năng (hoặc a lambda) bằng một kiểm tra cho điều kiện:

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

-3

Lót:

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

Nếu bạn không chắc chắn rằng bất kỳ yếu tố nào sẽ hợp lệ theo các tiêu chí, bạn nên kèm theo điều này try/exceptvì điều đó [0]có thể nâng cao IndexError.


LoạiError: đối tượng 'trình tạo' không thể mô tả được
Josh Lee

Xấu của tôi, nên được hiểu danh sách không phải là một trình tạo, cố định ... cảm ơn! :)
Mizipzor

2
Không có lý do để đánh giá toàn bộ lặp (có thể không thể). Nó là mạnh mẽ và hiệu quả hơn để sử dụng một trong những giải pháp khác được cung cấp.
Mike Graham
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.