Localizando o Índice de Elementos com Base em uma Condição Usando Compreensão de Lista Python


119

O código Python a seguir parece ser muito longo quando proveniente de um plano de fundo do Matlab

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

Quando em Matlab eu posso escrever:

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

Existe um método breve de escrever isso em Python, ou eu continuo com a versão longa?


Obrigado por todas as sugestões e explicações da justificativa para a sintaxe do Python.

Depois de encontrar o seguinte no site numpy, acho que encontrei uma solução que eu gosto:

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

A aplicação das informações desse site ao meu problema acima daria o seguinte:

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

O seguinte deve funcionar (mas não tenho um intérprete Python disponível para testá-lo):

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
Que tal [idx for idx in range(len(a)) if a[idx] > 2]? A razão pela qual isso é um pouco complicado de fazer em Python é porque ele não usa índices tanto quanto outras linguagens.
NullUserException

Respostas:


77
  • No Python, você não usaria índices para isso, mas lida apenas com os valores [value for value in a if value > 2]. Geralmente, lidar com índices significa que você não está fazendo algo da melhor maneira.

  • Se você não precisa de uma API semelhante ao Matlab de, você usaria numpy , um pacote para arrays multidimensionais e matemática numérica em Python que é fortemente inspirado em Matlab. Você usaria uma matriz numpy em vez de uma lista.

    >>> 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
você tem listas, uma para intervalos e outra para ângulos, deseja filtrar os valores dos intervalos acima de algum limite. Como você também filtra os ângulos correspondentes a esses intervalos da maneira "melhor"?
Mehdi

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

7
"No Python, você não usaria índices para isso, mas lide apenas com os valores". Esta declaração mostra que você não fez análises de dados suficientes e modelagem de aprendizado de máquina. Os índices de um tensor com base em determinada condição são usados ​​para filtrar outro tensor.
horaceT

63

Outra maneira:

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

Em geral, lembre-se de que, embora findseja uma função pronta para cozinhar, a compreensão da lista é uma solução geral e, portanto, muito poderosa . Nada impede que você escreva uma findfunção no Python e use-a mais tarde como desejar. Ou seja:

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

Observe que estou usando listas aqui para imitar o Matlab. Seria mais Pythonic usar geradores e iteradores.


2
O OP poderia ter escrito como [i for i,v in enumerate(a) if v > 2]antes.
NullUserException

Não é mais curto, é mais longo. Substitua indexpor ie valuecom vno original e conte os caracteres.
agf

@NullUser, agf: você está certo, mas o ponto principal é a segunda parte :)
Eli Bendersky

1
Usar enumeratemais range(len(...))é mais robusto e mais eficiente.
Mike Graham

1
@ Mike Graham: Eu concordo - vai mudar teh find_indicesfunção para usoenumerate
Eli Bendersky

22

Para mim, funciona bem:

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

6

Talvez outra pergunta seja: "o que você fará com esses índices depois de obtê-los?" Se você for usá-los para criar outra lista, no Python, eles serão um passo intermediário desnecessário. Se você deseja todos os valores que correspondem a uma determinada condição, basta usar o filtro interno:

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

Ou escreva sua própria lista:

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

Se você deseja removê-los da lista, a maneira Pythonic não é necessariamente removê-la da lista, mas escreva uma compreensão da lista como se estivesse criando uma nova lista e atribuindo-a de volta no local usando o listvar[:]lado esquerdo -lado:

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

O Matlab fornece findporque seu modelo centralizado em matriz funciona selecionando itens usando seus índices de matriz. Você pode fazer isso em Python, certamente, mas a maneira mais Pythonic é usar iteradores e geradores, como já mencionado por @EliBendersky.


Paul, ainda não encontrei a necessidade disso em um script / função / classe. É mais para testes interativos de uma classe que estou escrevendo.
Lee

@ Mike - obrigado pela edição, mas eu realmente quis dizer a[:] = ...- veja a resposta de Alex Martelli a esta pergunta stackoverflow.com/questions/1352885/… .
PaulMcG

@Paul, eu assumi (e esperava!) Que você realmente não quis dizer isso a partir da sua descrição que você iria "criar uma nova lista"; Acho que os programas tendem a ser mais ágeis em entender e manter quando modificam os dados existentes com moderação. De qualquer forma, lamento exceder o limite - você certamente poderá editar sua postagem para o que quiser.
Mike Graham

6

Mesmo que seja uma resposta tardia: acho que essa ainda é uma pergunta muito boa e o IMHO Python (sem bibliotecas ou kits de ferramentas adicionais como numpy) ainda não possui um método conveniente para acessar os índices dos elementos da lista de acordo com um filtro definido manualmente.

Você pode definir manualmente uma função, que fornece essa funcionalidade:

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

Rendimentos: [0, 4]

Na minha imaginação, o caminho perfeito seria criar uma classe de lista filho e adicionar os índices como método de classe. Dessa maneira, apenas o método de filtro seria necessário:

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)

Eu elaborei um pouco mais sobre esse tópico aqui: http://tinyurl.com/jajrr87

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.