Tìm chỉ mục của các phần tử dựa trên một điều kiện bằng cách sử dụng tính năng hiểu danh sách python


119

Mã Python sau dường như rất dài khi đến từ nền Matlab

>>> a = [1, 2, 3, 1, 2, 3]
>>> [index for index,value in enumerate(a) if value > 2]
[2, 5]

Khi ở trong Matlab, tôi có thể viết:

>> a = [1, 2, 3, 1, 2, 3];
>> find(a>2)
ans =
     3     6

Có phương pháp viết tay ngắn nào để viết điều này bằng Python không, hay tôi chỉ sử dụng phiên bản dài?


Cảm ơn bạn vì tất cả các đề xuất và giải thích về cơ sở lý luận cho cú pháp của Python.

Sau khi tìm thấy những điều sau trên trang web numpy, tôi nghĩ rằng tôi đã tìm thấy một giải pháp mà tôi thích:

http://docs.scipy.org/doc/numpy/user/basics.indexing.html#boolean-or-mask-index-arrays

Áp dụng thông tin từ trang web đó cho vấn đề của tôi ở trên, sẽ đưa ra kết quả sau:

>>> from numpy import array
>>> a = array([1, 2, 3, 1, 2, 3])
>>> b = a>2 
array([False, False, True, False, False, True], dtype=bool)
>>> r = array(range(len(b)))
>>> r(b)
[2, 5]

Sau đó, những điều sau sẽ hoạt động (nhưng tôi không có trình thông dịch Python trong tay để kiểm tra nó):

class my_array(numpy.array):
    def find(self, b):
        r = array(range(len(b)))
        return r(b)


>>> a = my_array([1, 2, 3, 1, 2, 3])
>>> a.find(a>2)
[2, 5]

6
Làm thế nào về [idx for idx in range(len(a)) if a[idx] > 2]? Lý do khiến điều này hơi khó xử trong Python là vì nó không sử dụng chỉ mục nhiều như các ngôn ngữ khác.
NullUserException

Câu trả lời:


77
  • Trong Python, bạn sẽ không sử dụng chỉ mục cho việc này, mà chỉ xử lý các giá trị— [value for value in a if value > 2]. Thông thường xử lý các chỉ mục có nghĩa là bạn đang không làm điều gì đó theo cách tốt nhất.

  • Nếu bạn làm cần một API tương tự như Matlab, bạn sẽ sử dụng NumPy , một gói cho mảng đa chiều và toán số bằng Python được lấy cảm hứng từ Matlab. Bạn sẽ sử dụng một mảng numpy thay vì một danh sách.

    >>> import numpy
    >>> a = numpy.array([1, 2, 3, 1, 2, 3])
    >>> a
    array([1, 2, 3, 1, 2, 3])
    >>> numpy.where(a > 2)
    (array([2, 5]),)
    >>> a > 2
    array([False, False,  True, False, False,  True], dtype=bool)
    >>> a[numpy.where(a > 2)]
    array([3, 3])
    >>> a[a > 2]
    array([3, 3])
    

2
bạn có danh sách, một cho phạm vi và một cho góc, bạn muốn lọc ra các giá trị phạm vi cao hơn một số ngưỡng. Làm cách nào để bạn cũng lọc các góc tương ứng với các phạm vi đó theo cách "tốt nhất"?
Mehdi

3
filtered_ranges_and_angles = [(range, angle) for range, angle in zip(ranges, angles) if should_be_kept(range)]
Mike Graham

7
"Trong Python, bạn sẽ không sử dụng các chỉ mục cho việc này, mà chỉ xử lý các giá trị" tuyên bố này cho thấy bạn chưa thực hiện đủ phân tích dữ liệu và mô hình học máy. Các chỉ số của một tensor dựa trên điều kiện nhất định được sử dụng để lọc tensor khác.
horaceT

63

Cách khác:

>>> [i for i in range(len(a)) if a[i] > 2]
[2, 5]

Nói chung, hãy nhớ rằng mặc dù findlà một hàm đã được nấu sẵn, nhưng việc hiểu danh sách là một cách tổng quát và do đó là một giải pháp rất hiệu quả . Không có gì ngăn cản bạn viết một findhàm bằng Python và sử dụng nó sau này như bạn muốn. I E:

>>> def find_indices(lst, condition):
...   return [i for i, elem in enumerate(lst) if condition(elem)]
... 
>>> find_indices(a, lambda e: e > 2)
[2, 5]

Lưu ý rằng tôi đang sử dụng danh sách ở đây để bắt chước Matlab. Sẽ là Pythonic hơn nếu sử dụng máy phát điện và máy lặp.


2
OP có thể đã viết nó [i for i,v in enumerate(a) if v > 2]thay thế.
NullUserException

Nó không ngắn hơn, nó dài hơn. Thay thế indexbằng ivaluebằng vtrong bản gốc và đếm các ký tự.
agf

@NullUser, AGF: bạn nói đúng, nhưng điểm chính là phần thứ hai :)
Eli Bendersky

1
Sử dụng enumeratehết range(len(...))sẽ mạnh mẽ hơn và hiệu quả hơn.
Mike Graham

1
@Mike Graham: Tôi đồng ý - sẽ thay đổi find_indiceshàm teh để sử dụngenumerate
Eli Bendersky

22

Đối với tôi, nó hoạt động tốt:

>>> import numpy as np
>>> a = np.array([1, 2, 3, 1, 2, 3])
>>> np.where(a > 2)[0]
[2 5]

6

Có thể một câu hỏi khác là, "bạn sẽ làm gì với những chỉ số đó một khi bạn có được chúng?" Nếu bạn định sử dụng chúng để tạo một danh sách khác, thì trong Python, chúng là một bước giữa không cần thiết. Nếu bạn muốn tất cả các giá trị phù hợp với một điều kiện nhất định, chỉ cần sử dụng bộ lọc nội trang:

matchingVals = filter(lambda x : x>2, a)

Hoặc viết tổng hợp danh sách của riêng bạn:

matchingVals = [x for x in a if x > 2]

Nếu bạn muốn xóa chúng khỏi danh sách, thì cách Pythonic là không nhất thiết phải xóa khỏi danh sách, mà hãy viết một cách hiểu danh sách như thể bạn đang tạo một danh sách mới và gán lại tại chỗ bằng cách sử dụng listvar[:]ở bên trái. -bên:

a[:] = [x for x in a if x <= 2]

Matlab cung cấp findbởi vì mô hình tập trung vào mảng của nó hoạt động bằng cách chọn các mục bằng cách sử dụng chỉ số mảng của chúng. Bạn có thể thực hiện điều này bằng Python, nhưng cách Pythonic nhiều hơn là sử dụng trình lặp và trình tạo, như đã được đề cập bởi @EliBendersky.


Paul, tôi chưa bắt gặp nhu cầu này trong script / function / class. Nó nhiều hơn để kiểm tra tương tác của một lớp học mà tôi đang viết.
Lee

@Mike - cảm ơn vì đã chỉnh sửa, nhưng tôi thực sự có ý a[:] = ...- hãy xem câu trả lời của Alex Martelli cho câu hỏi này stackoverflow.com/questions/1352885/… .
PaulMcG

@Paul, tôi cho rằng (và hy vọng!) Bạn không thực sự có ý nói rằng bạn sẽ "tạo một danh sách mới" trong mô tả của mình; Tôi thấy rằng các chương trình có xu hướng dễ hiểu và dễ bảo trì hơn khi chúng thay đổi dữ liệu hiện có một cách rất tiết kiệm. Trong bất kỳ trường hợp nào, tôi rất tiếc đã bỏ qua - bạn chắc chắn có thể chỉnh sửa bài đăng của mình trở lại bất kỳ điều gì bạn muốn.
Mike Graham

6

Ngay cả khi đó là một câu trả lời muộn: Tôi nghĩ đây vẫn là một câu hỏi rất hay và IMHO Python (không có thư viện hoặc bộ công cụ bổ sung như numpy) vẫn thiếu một phương pháp thuận tiện để truy cập các chỉ số của các phần tử danh sách theo một bộ lọc được xác định thủ công.

Bạn có thể xác định thủ công một chức năng, cung cấp chức năng đó:

def indices(list, filtr=lambda x: bool(x)):
    return [i for i,x in enumerate(list) if filtr(x)]

print(indices([1,0,3,5,1], lambda x: x==1))

Sản lượng: [0, 4]

Trong trí tưởng tượng của tôi, cách hoàn hảo sẽ là tạo một danh sách lớp con và thêm hàm chỉ số làm phương thức lớp. Theo cách này, chỉ cần phương pháp lọc:

class MyList(list):
    def __init__(self, *args):
        list.__init__(self, *args)
    def indices(self, filtr=lambda x: bool(x)):
        return [i for i,x in enumerate(self) if filtr(x)]

my_list = MyList([1,0,3,5,1])
my_list.indices(lambda x: x==1)

Tôi đã giải thích thêm một chút về chủ đề đó tại đây: http://tinyurl.com/jajrr87

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.