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

Я ожидал быНахождение индекса в Numpy массива в списке

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

вернуть

2 

, но вместо этого я получаю

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

что-нибудь лучше, чем мой текущее решение? Это кажется неэффективным.

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

Очень очень интересный. –

+1

Является ли это неоднородным списком в качестве примера, или у вас действительно есть список со многими различными типами в нем? – mgilson

+1

@mgilson только мой надуманный пример. Я работаю со списком массивов numpy равного размера – Lee88

ответ

11

Причина ошибки здесь, очевидно, потому, что ndarray Numpy переопределению == возвращает массив, а не логическое значение.

AFAIK, здесь нет простого решения. Следующие действия будут работать до тех пор, пока бит
np.all(val == array) будет работать.

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

ли работает этот бит или нет, зависит главным образом от того, что другие элементы в массиве, и если они могут быть сопоставлены с Numpy массивами.

+0

Обратите внимание, что это не идентично 'list.index', который выдает' ValueError', когда такого элемента нет. Но хорошее и простое решение! – MSeifert

+0

@MSeifert - Да. Я собирался для API, который немного больше напоминает 'str.find'. Если вы хотите исключение, вы можете просто отбросить часть '-1' (только передача генератора в' next'). В этом случае вы получите 'StopIteration', если он не был найден. – mgilson

2

Для производительности вы можете обрабатывать только массивы NumPy во входном списке. Таким образом, мы могли бы проверять тип перед переходом в цикл и индексом в элементы, которые являются массивами.

Таким образом, реализация будет -

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

К сожалению, список OP состоит только из массивов numpy для начала (на основе комментариев), так что это обеспечит дополнительные накладные расходы, чем оптимизация. –

+0

@MadPhysicist Действительно? Я думал, что у OP есть образец 'foo = [1," hello ", np.array ([[1,2,3]])]', который является смешанным. Я пропустил это упоминание о '.. .. list состоит только из нескольких массивов"? – Divakar

+0

Да. Третий комментарий по этому вопросу. Имеет смысл, что OP хотел получить самый общий ответ, поэтому он спросил с надуманным массивом, но он, к сожалению, бросает демпфер на ваш ответ. –

2

Как об этом?

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

Выход:

[2] 

Примечание: Это также будет работать, даже если foo список. Я просто положил его как массив numpy.

+0

OP сказал в комментариях, что настоящий список будет содержать только массивы, поэтому в лучшем случае это будет шаг предварительной обработки. –

+1

Технически первый метод работает только надежно, если в списке имеется ровно один массив, и вы знаете, что этот массив тот, который вы ищете. –

+0

Да. Разве вопрос OP не запрашивает индекс массива * a * numpy? – kmario23

2

Проблема здесь (вы, наверное, уже знаете, а просто повторить его), что list.index работы по линиям:

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

Линейка if item == wanted_item является проблемой, потому что она неявно преобразует item == wanted_item в булево. Но numpy.ndarray (за исключением, если это скаляр) поднимает этот ValueError то:

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

Решение 1: адаптер (тонкая оболочка) Класс

Я обычно используют тонкую оболочку (адаптер) вокруг numpy.ndarray всякий раз, когда мне нужно использовать функции питона как 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) 

Эти тонкие оболочки являются более дорогими, чем вручную с помощью некоторых enumerate цикла или понимания, но вы не должны г e-реализовать функции python.Если предположить, что список содержит только Numpy-массивы (в противном случае вы должны сделать некоторые if ... else ... проверки):

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

После этого шага вы можете использовать все функции питона в этом списке:

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

Эти упаковщики а не numpy-массивы, но у вас тонкие обертки, поэтому дополнительный список довольно мал. Таким образом, в зависимости от ваших потребностей вы можете сохранить обернутый список и исходный список и выбрать, на которых делать операции, например, вы можете также list.count одинаковые массивы сейчас:

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

или 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.])] 

Решение 2: подкласс и ndarray.view

Этот подход использует явные подклассы numpy.array. Это имеет то преимущество, что вы получите все-массив встроенной функциональности и только изменять запрошенную операцию (которая будет __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 

Опять вы получите самые современные методы перечисляю этот путь: list.remove, list.count кроме list.index.

Однако этот подход может привести к тонкому поведению, если какая-либо операция неявно использует __eq__. Вы всегда можете повторно интерпретировать это как обычный Numpy массив с помощью np.asarray или .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.]) 

Альтернатива: Перекрытие __bool__ (или __nonzero__ для питона 2)

Вместо фиксации проблемы в __eq__ методе вы могли бы переопределить __bool__ или __nonzero__:

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

    __nonzero__ = __bool__ 

Опять же это делает list.index работу как Предназначение:

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

Но это определенно изменит больше поведения! Например:

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

Я продолжал искать способ переопределить класс массива, но это не помогло бы с существующими объектами. Очень приятное решение. –

+0

@MadPhysicist Да, можно также работать с ['ndarray.view'] (https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.view.html) и подкласс, переопределяющий' __eq__ '. Шаги остаются неизменными, вам нужно пройти один проход, чтобы создать список этих представлений перед применением операций 'list.index'. – MSeifert

+0

Да, с тем преимуществом, что вы могли бы использовать список подкласса в качестве своего единственного списка без дополнительной модификации окружающего кода. –

0

Это должно сделать работу:

[i for i,j in enumerate(foo) if j.__class__.__name__=='ndarray'] 
[2] 
Смежные вопросы