2016-12-21 33 views
18
import numpy as np 
foo = [1, "hello", np.array([[1,2,3]]) ] 

Tôi mong chờTìm chỉ số của một mảng NumPy trong một danh sách

foo.index(np.array([[1,2,3]])) 

trở

2 

nhưng thay vào đó tôi nhận được

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

bất cứ điều gì tốt hơn so với tôi giải pháp tạm thời? Có vẻ như không hiệu quả.

def find_index_of_array(list, array): 
    for i in range(len(list)): 
     if np.all(list[i]==array): 
      return i 

find_index_of_array(foo, np.array([[1,2,3]])) 
# 2 
+0

Rất rất thú vị. –

+1

Danh sách không đồng nhất chỉ là một ví dụ, hay bạn thực sự có một danh sách với nhiều loại khác nhau trong nó? – mgilson

+1

@mgilson chỉ là ví dụ giả tạo của tôi. Tôi đang làm việc với một danh sách các mảng numpy có kích thước bằng nhau – Lee88

Trả lời

11

Lý do cho sự lỗi ở đây là rõ ràng vì ndarray NumPy của đè == để trả lại một mảng chứ không phải là một boolean.

AFAIK, không có giải pháp đơn giản nào ở đây. Sau đây sẽ hoạt động miễn là các bit
np.all(val == array) hoạt động.

next((i for i, val in enumerate(lst) if np.all(val == array)), -1) 

Liệu bit đó hoạt động hay không phụ thuộc nhiều vào những yếu tố khác trong mảng và liệu chúng có thể được so sánh với mảng không.

+0

Lưu ý rằng điều này không giống với 'list.index' mà ném một' ValueError' khi không có mục đó. Nhưng giải pháp tốt và đơn giản! – MSeifert

+0

@MSeifert - Yeah. Tôi đã đi cho một API đó là một chút giống như 'str.find'. Nếu bạn muốn có một ngoại lệ thì bạn chỉ có thể thả phần '-1' (chỉ truyền bộ tạo đến' next'). Trong trường hợp đó, bạn sẽ nhận được một 'StopIteration' nếu nó không được tìm thấy. – mgilson

2

Để thực hiện, bạn có thể chỉ muốn xử lý các mảng NumPy trong danh sách đầu vào. Vì vậy, chúng ta có thể gõ-kiểm tra trước khi đi vào vòng lặp và chỉ mục vào các phần tử là mảng.

Như vậy, một thực hiện sẽ là -

def find_index_of_array_v2(list1, array1): 
    idx = np.nonzero([type(i).__module__ == np.__name__ for i in list1])[0] 
    for i in idx: 
     if np.all(list1[i]==array1): 
      return i 
+0

Thật không may, danh sách của OP chỉ bao gồm các mảng numpy để bắt đầu (dựa trên ý kiến), vì vậy điều này sẽ cung cấp nhiều chi phí hơn tối ưu hóa. –

+0

@MadPhysicist thực sự? Tôi nghĩ OP có một mẫu 'foo = [1," hello ", np.array ([[1,2,3]])]', là một mẫu hỗn hợp. Tôi đã bỏ lỡ đề cập đến danh sách '" .. chỉ bao gồm các mảng có nhiều mảng ""? – Divakar

+0

Vâng. Nhận xét thứ ba về câu hỏi. Nó có ý nghĩa rằng OP muốn câu trả lời chung nhất, đó là lý do tại sao ông hỏi với mảng contrived, nhưng nó không ném một van điều tiết trên câu trả lời của bạn không may. –

2

Làm thế nào về việc này?

arr = np.array([[1,2,3]]) 
foo = np.array([1, 'hello', arr], dtype=np.object) 

# if foo array is of heterogeneous elements (str, int, array) 
[idx for idx, el in enumerate(foo) if type(el) == type(arr)] 

# if foo array has only numpy arrays in it 
[idx for idx, el in enumerate(foo) if np.array_equal(el, arr)] 

Output:

[2] 

Lưu ý: Điều này cũng sẽ làm việc ngay cả khi foo là một danh sách. Tôi chỉ cần đặt nó như là một mảng numpy ở đây.

+0

OP cho biết trong ý kiến ​​rằng danh sách thực sự sẽ chỉ chứa mảng, vì vậy đây sẽ là một bước tiền xử lý tốt nhất. –

+1

Về mặt kỹ thuật, phương thức đầu tiên chỉ hoạt động đáng tin cậy nếu có chính xác một mảng trong danh sách và bạn biết trước rằng mảng đó là mảng bạn đang tìm kiếm. –

+0

Có. Không phải câu hỏi của OP yêu cầu chỉ mục của mảng * a * numpy? – kmario23

2

Vấn đề ở đây (bạn có thể biết đã nhưng chỉ để lặp lại nó) là list.index công trình dọc theo dòng:

for idx, item in enumerate(your_list): 
    if item == wanted_item: 
     return idx 

Dòng if item == wanted_item là vấn đề, bởi vì nó ngầm chuyển đổi item == wanted_item để một boolean. Nhưng numpy.ndarray (trừ khi đó là một vô hướng) làm tăng ValueError này sau đó:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Giải pháp 1: bộ chuyển đổi (wrapper mỏng) lớp

Tôi thường sử dụng một wrapper mỏng (bộ chuyển đổi) xung quanh numpy.ndarray bất cứ khi nào tôi cần phải sử dụng chức năng python như list.index:

class ArrayWrapper(object): 

    __slots__ = ["_array"] # minimizes the memory footprint of the class. 

    def __init__(self, array): 
     self._array = array 

    def __eq__(self, other_array): 
     # array_equal also makes sure the shape is identical! 
     # If you don't mind broadcasting you can also use 
     # np.all(self._array == other_array) 
     return np.array_equal(self._array, other_array) 

    def __array__(self): 
     # This makes sure that `np.asarray` works and quite fast. 
     return self._array 

    def __repr__(self): 
     return repr(self._array) 

Những giấy gói mỏng đắt hơn so với cách thủ sử dụng một số enumerate vòng lặp hoặc hiểu nhưng bạn không cần phải r e-thực hiện các chức năng python.Giả sử danh sách chứa chỉ NumPy-mảng (nếu không bạn cần phải làm một số if ... else ... kiểm tra):

list_of_wrapped_arrays = [ArrayWrapper(arr) for arr in list_of_arrays] 

Sau bước này bạn có thể sử dụng tất cả chức năng python của bạn trong danh sách này:

>>> list_of_arrays = [np.ones((3, 3)), np.ones((3)), np.ones((3, 3)) * 2, np.ones((3))] 
>>> list_of_wrapped_arrays.index(np.ones((3,3))) 
0 
>>> list_of_wrapped_arrays.index(np.ones((3))) 
1 

Những giấy gói là không còn mảng nữa nhưng bạn có các trình bao bọc mỏng nên danh sách bổ sung là khá nhỏ. Vì vậy, tùy thuộc vào nhu cầu của bạn, bạn có thể giữ cho danh sách bọc và danh sách ban đầu và chọn trên đó để làm các hoạt động, ví dụ bạn có thể cũng list.count các mảng giống hệt bây giờ:

>>> list_of_wrapped_arrays.count(np.ones((3))) 
2 

hoặc list.remove:

>>> list_of_wrapped_arrays.remove(np.ones((3))) 
>>> list_of_wrapped_arrays 
[array([[ 1., 1., 1.], 
     [ 1., 1., 1.], 
     [ 1., 1., 1.]]), 
array([[ 2., 2., 2.], 
     [ 2., 2., 2.], 
     [ 2., 2., 2.]]), 
array([ 1., 1., 1.])] 

Giải pháp 2: phân lớp và ndarray.view

Cách tiếp cận này sử dụng các lớp con rõ ràng là numpy.array. Nó có ưu điểm là bạn nhận được tất cả được xây dựng trong mảng chức năng và chỉ sửa đổi các hoạt động yêu cầu (đó sẽ là __eq__):

class ArrayWrapper(np.ndarray): 
    def __eq__(self, other_array): 
     return np.array_equal(self, other_array) 

>>> your_list = [np.ones(3), np.ones(3)*2, np.ones(3)*3, np.ones(3)*4] 

>>> view_list = [arr.view(ArrayWrapper) for arr in your_list] 

>>> view_list.index(np.array([2,2,2])) 
1 

Một lần nữa bạn nhận được hầu hết các phương pháp danh sách theo cách này: list.remove, list.count ngoài list.index.

Tuy nhiên cách tiếp cận này có thể mang lại hành vi tinh vi nếu một số thao tác sử dụng ngầm sử dụng __eq__. Bạn luôn có thể giải thích là như mảng NumPy đơn giản bằng cách sử dụng np.asarray hoặc .view(np.ndarray):

>>> view_list[1] 
ArrayWrapper([ 2., 2., 2.]) 

>>> view_list[1].view(np.ndarray) 
array([ 2., 2., 2.]) 

>>> np.asarray(view_list[1]) 
array([ 2., 2., 2.]) 

Alternative: Overriding __bool__ (hoặc __nonzero__ cho python 2)

Thay vì sửa chữa các vấn đề trong phương pháp __eq__ bạn có thể cũng ghi đè __bool__ hoặc __nonzero__:

class ArrayWrapper(np.ndarray): 
    # This could also be done in the adapter solution. 
    def __bool__(self): 
     return bool(np.all(self)) 

    __nonzero__ = __bool__ 

Một lần nữa điều này làm cho công việc list.index như dự định:

>>> your_list = [np.ones(3), np.ones(3)*2, np.ones(3)*3, np.ones(3)*4] 
>>> view_list = [arr.view(ArrayWrapper) for arr in your_list] 
>>> view_list.index(np.array([2,2,2])) 
1 

Nhưng điều này chắc chắn sẽ sửa đổi nhiều hành vi hơn! Ví dụ:

>>> if ArrayWrapper([1,2,3]): 
...  print('that was previously impossible!') 
that was previously impossible! 
+0

Tôi tiếp tục tìm kiếm một cách để ghi đè lên lớp mảng, nhưng điều đó sẽ không giúp đỡ với các đối tượng hiện có. Giải pháp rất đẹp. –

+0

@MadPhysicist Vâng, người ta cũng có thể làm việc với ['ndarray.view'] (https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.view.html) và một lớp con ghi đè' __eq__ '. Các bước vẫn giữ nguyên, bạn cần một pass để tạo một danh sách các khung nhìn này trước khi áp dụng các hoạt động 'list.index'. – MSeifert

+0

Có, với lợi thế mà bạn có thể sử dụng danh sách lớp con làm danh sách duy nhất của bạn mà không cần sửa đổi thêm mã xung quanh. –

0

này nên thực hiện công việc:

[i for i,j in enumerate(foo) if j.__class__.__name__=='ndarray'] 
[2] 
Các vấn đề liên quan