it-swarm-pt.tech

Encontre o primeiro elemento em uma sequência que corresponda a um predicado

Eu quero uma maneira idiomática para encontrar o primeiro elemento em uma lista que corresponda a um predicado.

O código atual é bem feio:

[x for x in seq if predicate(x)][0]

Eu pensei em mudar isso para:

from itertools import dropwhile
dropwhile(lambda x: not predicate(x), seq).next()

Mas deve haver algo mais elegante ... E seria legal se ele retornasse um valor None em vez de gerar uma exceção se nenhuma correspondência fosse encontrada.

Eu sei que eu poderia apenas definir uma função como:

def get_first(predicate, seq):
    for i in seq:
        if predicate(i): return i
    return None

Mas é muito insípido começar a preencher o código com funções utilitárias como essa (e as pessoas provavelmente não perceberão que já estão lá, então elas tendem a se repetir ao longo do tempo) se houver ins construídos que já fornecem o mesmo.

145
fortran

next(x for x in seq if predicate(x))

Ele gera StopIteration, se não houver nenhum.

next(ifilter(predicate, seq), None)

retorna None se não houver tal elemento.

200
jfs

Você poderia usar uma expressão de gerador com um valor padrão e, em seguida, next it:

next((x for x in seq if predicate(x)), None)

Embora para este one-liner você precise estar usando Python> = 2.6.

Este artigo bastante popular discute ainda mais este problema: Cleanest Python função find-in-list? .

83
Chewie

Não acho que haja algo errado com as soluções que você propôs em sua pergunta.

No meu próprio código, eu implementaria assim:

(x for x in seq if predicate(x)).next()

A sintaxe com () cria um gerador, que é mais eficiente do que gerar toda a lista de uma só vez com [].

5
mac

A resposta de J. F. Sebastian é mais elegante, mas requer python 2.6 como fortran apontado.

Para Python versão <2.6, aqui está o melhor que eu posso fazer:

from itertools import repeat,ifilter,chain
chain(ifilter(predicate,seq),repeat(None)).next()

Alternativamente, se você precisasse de uma lista mais tarde (a lista lida com o StopIteration), ou se você precisasse de mais do que apenas o primeiro, mas ainda não todos, você pode fazer isso com o islice:

from itertools import islice,ifilter
list(islice(ifilter(predicate,seq),1))

ATUALIZAÇÃO: Embora eu esteja pessoalmente usando uma função predefinida chamada first () que captura um StopIteration e retorna None, aqui está uma possível melhoria em relação ao exemplo acima: evite usar filter/ifilter:

from itertools import islice,chain
chain((x for x in seq if predicate(x)),repeat(None)).next()
1
parity3