Python: Localizar na lista

586

Eu me deparei com isso:

item = someSortOfSelection()
if item in myList:
    doMySpecialFunction(item)

mas às vezes não funciona com todos os meus itens, como se não fossem reconhecidos na lista (quando é uma lista de cadeias).

Essa é a maneira mais 'pitônica' de encontrar um item em uma lista if x in l::?

Stephane Rolland
fonte
3
Está perfeitamente bem e deve funcionar se o item for igual a um dos elementos internos myList.
precisa
1
você quer dizer que foi a boa maneira de fazer as coisas? em meus vários ensaios, talvez houvesse espaços em branco, e alimentações de linha intereferring ... eu só queria ter certeza de que é a boa maneira de implementar "encontrar em lista" (em geral)
Stephane Rolland

Respostas:

1174

Quanto à sua primeira pergunta: esse código é perfeitamente adequado e deve funcionar se for itemigual a um dos elementos internos myList. Talvez você tente encontrar uma string que não corresponda exatamente a um dos itens ou talvez esteja usando um valor flutuante que sofra imprecisão.

Quanto à sua segunda pergunta: Na verdade, existem várias maneiras possíveis de "encontrar" coisas nas listas.

Verificando se algo está dentro

Este é o caso de uso que você descreve: Verificando se algo está dentro de uma lista ou não. Como você sabe, você pode usar o inoperador para isso:

3 in [1, 2, 3] # => True

Filtrando uma coleção

Ou seja, localizando todos os elementos em uma sequência que atendem a uma determinada condição. Você pode usar compreensão de lista ou expressões geradoras para isso:

matches = [x for x in lst if fulfills_some_condition(x)]
matches = (x for x in lst if x > 6)

O último retornará um gerador que você pode imaginar como uma espécie de lista lenta que só será criada assim que você iterar. A propósito, o primeiro é exatamente equivalente a

matches = filter(fulfills_some_condition, lst)

no Python 2. Aqui você pode ver funções de ordem superior em funcionamento. No Python 3, filternão retorna uma lista, mas um objeto semelhante ao gerador.

Localizando a primeira ocorrência

Se você deseja apenas a primeira coisa que corresponda a uma condição (mas ainda não sabe o que é), é bom usar um loop for (possivelmente usando a elsecláusula também, o que não é muito conhecido). Você também pode usar

next(x for x in lst if ...)

que retornará a primeira partida ou aumentará a StopIterationse nenhuma for encontrada. Como alternativa, você pode usar

next((x for x in lst if ...), [default value])

Localizando a localização de um item

Para listas, também há o indexmétodo que às vezes pode ser útil se você quiser saber onde um determinado elemento está na lista:

[1,2,3].index(2) # => 1
[1,2,3].index(4) # => ValueError

No entanto, observe que, se você tiver duplicatas, .indexsempre retornará o índice mais baixo: ......

[1,2,3,2].index(2) # => 1

Se houver duplicatas e você desejar todos os índices, poderá usar enumerate():

[i for i,x in enumerate([1,2,3,2]) if x==2] # => [1, 3]
Niklas B.
fonte
10
Stephane: Permitam-me reformular: nãoif x in list é o que as pessoas reclamam por não serem uma função embutida. Eles reclamam do fato de não haver uma maneira explícita de encontrar a primeira ocorrência de algo em uma lista que corresponde a uma determinada condição. Mas, como indicado na minha resposta, pode ser (ab) usado para isso. next()
precisa
3
@ Stephanie: O segundo não gera uma tupla, mas um gerador (que é uma lista ainda não construída, basicamente). Se você deseja usar o resultado apenas uma vez, geralmente é preferível um gerador. No entanto, se você desejar usar a coleção criada várias vezes depois, é recomendável criar uma lista explícita em primeiro lugar. Ter um olhar para a minha atualização, é agora um pouco melhor estruturado :)
Niklas B.
26
Seu exemplo de "localização da primeira ocorrência" é de ouro. Se sente mais Pythonic do que a [list comprehension...][0]abordagem
acjay
4
Estou cada vez mais desapontado com os recursos 'funcionais' do python. No haskell, há a função find no módulo Data.List que faz exatamente isso. Mas, em python, não é e é pequeno demais para torná-lo uma biblioteca, para que você precise reimplementar a mesma lógica repetidamente. O que é um desperdício ...
user1685095
3
Seria bom se houvesse um kwarg index()chamado keyque funcionasse como o keyaceito por max(); por exemplo: index(list, key=is_prime).
Curt
189

Se você deseja encontrar um elemento ou Noneusar o padrão next, ele não aumentará StopIterationse o item não for encontrado na lista:

first_or_default = next((x for x in lst if ...), None)
Janusz Skonieczny
fonte
1
nextusa um iterador como o primeiro parâmetro e uma lista / tupla NÃO é um iterador. Por isso, deve ser first_or_default = next(iter([x for x in lst if ...]), None)ver docs.python.org/3/library/functions.html#next
Devy
7
@ Devy: isso mesmo, mas (x for x in lst if ...)é um gerador sobre a lista lst(que é um iterador). Se você fizer isso next(iter([x for x in lst if ...]), None), precisará construir a lista [x for x in lst if ...], que será uma operação muito mais cara.
Erlend Graff
1
Há uma abstração aqui para definir uma função de localização. Apenas encapsule a expulsão booleana do ifem um lambda e você pode escrever find(fn,list)normalmente em vez de ofuscar o código do gerador.
Semiomant 29/03
22

Embora a resposta de Niklas B. seja bastante abrangente, quando queremos encontrar um item em uma lista, às vezes é útil obter seu índice:

next((i for i, x in enumerate(lst) if [condition on x]), [default value])
Vincent Cantin
fonte
11

Localizando a primeira ocorrência

Há uma receita para isso em itertools:

def first_true(iterable, default=False, pred=None):
    """Returns the first true value in the iterable.

    If no true value is found, returns *default*

    If *pred* is not None, returns the first item
    for which pred(item) is true.

    """
    # first_true([a,b,c], x) --> a or b or c or x
    # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
    return next(filter(pred, iterable), default)

Por exemplo, o código a seguir localiza o primeiro número ímpar em uma lista:

>>> first_true([2,3,4,5], None, lambda x: x%2==1)
3  
Antony Hatchkins
fonte
6

Outra alternativa: você pode verificar se um item está em uma lista com if item in list:, mas esta é a ordem O (n). Se você está lidando com grandes listas de itens e tudo o que precisa saber é se algo é membro da sua lista, você pode converter a lista em um conjunto primeiro e tirar proveito da pesquisa constante do conjunto de tempo :

my_set = set(my_list)
if item in my_set:  # much faster on average than using a list
    # do something

Não será a solução correta em todos os casos, mas, em alguns casos, isso pode oferecer um melhor desempenho.

Observe que criar o conjunto com set(my_list)também é O (n); portanto, se você precisar fazer isso apenas uma vez, não será mais rápido fazê-lo dessa maneira. Se você precisar verificar repetidamente a associação, será O (1) para todas as pesquisas após a criação inicial do conjunto.

Engineero
fonte
4

Você pode usar uma das duas pesquisas possíveis enquanto trabalha com a lista de cadeias de caracteres:

  1. se o elemento da lista for igual a um item ('exemplo' está em ['um', 'exemplo', 'dois']):

    if item in your_list: some_function_on_true()

    'ex' em ['um', 'ex', 'dois'] => Verdadeiro

    'ex_1' em ['um', 'ex', 'dois'] => Falso

  2. se o elemento da lista for como um item ('ex' está em ['um,' exemplo ',' dois '] ou' exemplo_1 'está em [' um ',' exemplo ',' dois ']):

    matches = [el for el in your_list if item in el]

    ou

    matches = [el for el in your_list if el in item]

    basta verificar len(matches)ou ler se necessário.

Alexey Antonenko
fonte
3

Definição e Uso

o count()método retorna o número de elementos com o valor especificado.

Sintaxe

list.count(value)

exemplo:

fruits = ['apple', 'banana', 'cherry']

x = fruits.count("cherry")

Exemplo da pergunta:

item = someSortOfSelection()

if myList.count(item) >= 1 :

    doMySpecialFunction(item)
josef
fonte
2
Isso é eficiente em uma lista muito longa? Diga a lista de um milhão?
3kstc 10/11/19
1
Não tenho certeza !!!
Josef
1

Em vez de usar o list.index(x)que retorna o índice de x se ele for encontrado na lista ou retorna uma #ValueErrormensagem se x não for encontrado, você pode usar o list.count(x)que retorna o número de ocorrências de x na lista (validação de que x está realmente na lista) ou retorna 0 caso contrário (na ausência de x). O legal count()é que ele não quebra seu código ou exige que você lance uma exceção para quando x não for encontrado

Taylor
fonte
e o ruim é que conta elementos. Não para quando o elemento é encontrado. por isso o desempenho é ruim em grandes listas
Jean-François Fabre
1

Se você vai verificar se o valor existe no colecionável uma vez, usar o operador 'in' é bom. No entanto, se você for verificar mais de uma vez, recomendo o uso do módulo bisect. Lembre-se de que os dados do módulo bisect devem ser classificados. Assim, você classifica os dados uma vez e depois pode usar a divisão. O uso do módulo bisect na minha máquina é cerca de 12 vezes mais rápido que o uso do operador 'in'.

Aqui está um exemplo de código usando a sintaxe Python 3.8 e acima:

import bisect
from timeit import timeit

def bisect_search(container, value):
    return (
      (index := bisect.bisect_left(container, value)) < len(container) 
      and container[index] == value
    )

data = list(range(1000))
# value to search
true_value = 666
false_value = 66666

# times to test
ttt = 1000

print(f"{bisect_search(data, true_value)=} {bisect_search(data, false_value)=}")

t1 = timeit(lambda: true_value in data, number=ttt)
t2 = timeit(lambda: bisect_search(data, true_value), number=ttt)

print("Performance:", f"{t1=:.4f}, {t2=:.4f}, diffs {t1/t2=:.2f}")

Resultado:

bisect_search(data, true_value)=True bisect_search(data, false_value)=False
Performance: t1=0.0220, t2=0.0019, diffs t1/t2=11.71
Vlad Bezden
fonte
0

Verifique se não há espaço em branco adicional / indesejado nos itens da lista de cadeias. Esse é um motivo que pode estar interferindo na explicação dos itens não encontrados.

Stephane Rolland
fonte