Подтвердить что ты не робот

Поиск индекса массива numpy в списке

import numpy as np
foo = [1, "hello", np.array([[1,2,3]]) ]

Я ожидал бы

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

для возврата

2

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

ValueError: значение истинности массива с более чем одним элементом неоднозначный. Используйте a.any() или 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
4b9b3361

Ответ 1

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

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

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

Независимо от того, работает этот бит или нет, критически зависит от того, что другие элементы в массиве, и если их можно сравнить с массивами numpy.

Ответ 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

Ответ 3

Как насчет этого?

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.

Ответ 4

Проблема здесь (вы, вероятно, уже знаете, но только для ее повторения) заключается в том, что 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: значение истинности массива с более чем одним элементом неоднозначно. Используйте a.any() или a.all()

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

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

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

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

>>> 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__ для python 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!

Ответ 5

Это должно выполнить задание:

[i for i,j in enumerate(foo) if j.__class__.__name__=='ndarray']
[2]