Qual é a melhor maneira, esteticamente e do ponto de vista de desempenho, para dividir uma lista de itens em várias listas com base em uma condicional? O equivalente a:
good = [x for x in mylist if x in goodvals]
bad = [x for x in mylist if x not in goodvals]
existe uma maneira mais elegante de fazer isso?
Atualização: aqui está o caso de uso real, para explicar melhor o que estou tentando fazer:
# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims = [f for f in files if f[2].lower() not in IMAGE_TYPES]
str.split()
, para dividir a lista em uma coleção ordenada de sub-listas consecutivas. Por exemplosplit([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])
, ao contrário de dividir os elementos de uma lista por categoria.IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')
. n (1) em vez de n (o / 2), praticamente sem diferença na legibilidade.Respostas:
Esse código é perfeitamente legível e extremamente claro!
Novamente, isso está bem!
Pode haver pequenas melhorias no desempenho usando conjuntos, mas é uma diferença trivial, e acho a compreensão da lista muito mais fácil de ler, e você não precisa se preocupar com a ordem que está sendo confundida, com as duplicatas removidas etc.
Na verdade, posso dar outro passo "para trás" e usar apenas um loop for simples:
A compreensão ou o uso de uma lista
set()
é bom até que você precise adicionar outra verificação ou outra lógica - digamos que você queira remover todos os jpegs de 0 byte, basta adicionar algo como ..fonte
[x for x in blah if ...]
- verbosa,lambda
desajeitada e limitada ... Parece como dirigir o carro mais legal de 1995 hoje. Não é o mesmo que naquela época.fonte
good.append(x) if x in goodvals else bad.append(x)
é mais legível.x
, você pode transformá-lo emappend
apenas um :for x in mylist: (good if isgood(x) else bad).append(x)
(bad.append, good.append)
(good if x in goodvals else bad).append(x)
Aqui está a abordagem do iterador lento:
Ele avalia a condição uma vez por item e retorna dois geradores, primeiro produzindo valores da sequência em que a condição é verdadeira, e o outro em que é falso.
Por ser preguiçoso, você pode usá-lo em qualquer iterador, mesmo que infinito:
Geralmente, embora a abordagem de retorno de lista não lenta seja melhor:
Edit: Para o seu caso mais específico de dividir itens em listas diferentes por alguma tecla, aqui está uma função genérica que faz isso:
Uso:
fonte
[ x for x in my_list if ExpensiveOperation(x) ]
levar muito tempo para ser executado, você certamente não deseja fazer isso duas vezes!tee
armazena todos os valores entre os iteradores que ele retorna, para que não economize memória se você fizer um loop sobre um gerador inteiro e depois o outro.O problema com todas as soluções propostas é que ele varrerá e aplicará a função de filtragem duas vezes. Eu faria uma função pequena e simples como esta:
Dessa forma, você não está processando nada duas vezes e também não está repetindo o código.
fonte
Minha opinião sobre isso. Proponho uma função lenta, de passagem única
partition
, que preserva a ordem relativa nas subsequências de saída.1. Requisitos
Presumo que os requisitos sejam:
i
)filter
ougroupby
)2.
split
bibliotecaMinha
partition
função (apresentada abaixo) e outras funções semelhantes a transformaram em uma pequena biblioteca:É instalável normalmente via PyPI:
Para dividir uma lista com base na condição, use a
partition
função:3.
partition
função explicadaInternamente, precisamos construir duas subsequências ao mesmo tempo, portanto consumir apenas uma sequência de saída forçará a outra a ser computada também. E precisamos manter o estado entre as solicitações do usuário (armazenamento processado, mas ainda não os elementos solicitados). Para manter o estado, eu uso duas filas de extremidade dupla (
deques
):SplitSeq
A classe cuida da limpeza:A mágica acontece em seu
.getNext()
método. É quase como.next()
nos iteradores, mas permite especificar que tipo de elemento queremos neste momento. Nos bastidores, ele não descarta os elementos rejeitados, mas os coloca em uma das duas filas:O usuário final deve usar a
partition
função Ele pega uma função de condição e uma sequência (exatamente comomap
oufilter
) e retorna dois geradores. O primeiro gerador cria uma subsequência de elementos para os quais a condição se mantém, o segundo cria a subsequência complementar. Iteradores e geradores permitem uma divisão lenta de sequências longas ou infinitas.Eu escolhi a função de teste como o primeiro argumento para facilitar a aplicação parcial no futuro (semelhante a como
map
efilter
ter a função de teste como o primeiro argumento).fonte
Eu basicamente gosto da abordagem de Anders, pois é muito geral. Aqui está uma versão que coloca o categorizador em primeiro lugar (para corresponder à sintaxe do filtro) e usa um padrão (suposto importado).
fonte
Primeiro acesso (edição pré-OP): use conjuntos:
Isso é bom tanto para legibilidade (IMHO) quanto para desempenho.
Segundo acesso (edição pós-OP):
Crie sua lista de boas extensões como um conjunto:
e isso aumentará o desempenho. Caso contrário, o que você tem parece bom para mim.
fonte
goodvals
.O itertools.groupby quase faz o que você deseja, exceto que exige que os itens sejam classificados para garantir que você obtenha um único intervalo contíguo; portanto, você deve classificar primeiro sua chave (caso contrário, obterá vários grupos intercalados para cada tipo). por exemplo.
dá:
Semelhante às outras soluções, a tecla func pode ser definida para se dividir em qualquer número de grupos que você desejar.
fonte
Esta resposta elegante e concisa de @dansalmo apareceu enterrada nos comentários, por isso estou apenas reposicionando-a aqui como uma resposta para que possa obter o destaque que merece, especialmente para novos leitores.
Exemplo completo:
fonte
Se você quiser fazer isso no estilo FP:
Não é a solução mais legível, mas pelo menos itera a lista apenas uma vez.
fonte
Pessoalmente, eu gosto da versão que você citou, supondo que você já tenha uma lista
goodvals
por aí. Caso contrário, algo como:Claro, isso é realmente muito parecido com o uso de uma compreensão de lista como você originalmente, mas com uma função em vez de uma pesquisa:
Em geral, acho a estética da compreensão de lista muito agradável. Obviamente, se você realmente não precisa preservar a ordem e não precisa de duplicatas, o uso dos métodos
intersection
edifference
nos conjuntos também funcionaria bem.fonte
filter(lambda x: is_good(x), mylist)
pode ser reduzido afilter(is_good, mylist)
Eu acho que uma generalização de dividir um iterável com base em N condições é útil
Por exemplo:
Se o elemento puder atender a várias condições, remova a interrupção.
fonte
Verifique isto
fonte
Às vezes, parece que a compreensão da lista não é a melhor coisa a se usar!
Fiz um pequeno teste com base na resposta que as pessoas deram a esse tópico, testado em uma lista gerada aleatoriamente. Aqui está a geração da lista (provavelmente existe uma maneira melhor de fazer, mas não é esse o ponto):
E aqui vamos nós
Usando a função cmpthese , o melhor resultado é a resposta dbr:
fonte
Mais uma solução para esse problema. Eu precisava de uma solução o mais rápido possível. Isso significa apenas uma iteração na lista e, de preferência, O (1) para adicionar dados a uma das listas resultantes. Isso é muito semelhante à solução fornecida pela sastanina , exceto muito mais curta:
Em seguida, você pode usar a função da seguinte maneira:
Se você não está bem com o resultado
deque
objeto, você pode facilmente convertê-lo paralist
,set
, o que você como (por exemplolist(lower)
). A conversão é muito mais rápida, que a construção das listas diretamente.Este método mantém a ordem dos itens, bem como as duplicatas.
fonte
Por exemplo, dividir a lista por pares e ímpares
Ou em geral:
Vantagens:
Desvantagens
fonte
Inspirados na ótima resposta (mas concisa!) Do @ gnibbler , podemos aplicar essa abordagem ao mapeamento para várias partições:
Em seguida,
splitter
pode ser usado da seguinte maneira:Isso funciona para mais de duas partições com um mapeamento mais complicado (e também nos iteradores):
Ou usando um dicionário para mapear:
fonte
append retorna None, então funciona.
fonte
Para o desempenho, tente
itertools
.Veja itertools.ifilter ou imap.
fonte
[x for x in a if x > 50000]
em uma matriz simples de 100000 números inteiros (via random.shuffle),filter(lambda x: x> 50000, a)
levará o dobro do tempo,ifilter(lambda x: x> 50000, a); list(result)
leva cerca de 2,3x de comprimento. Estranho mas verdade.Às vezes você não precisará dessa outra metade da lista. Por exemplo:
fonte
Esta é a maneira mais rápida.
Ele usa
if else
(como a resposta do dbr), mas cria um conjunto primeiro. Um conjunto reduz o número de operações de O (m * n) para O (log m) + O (n), resultando em um aumento de 45% na velocidade.Um pouco mais curto:
Resultados de referência:
O código de referência completo para Python 3.7 (modificado a partir FunkySayu):
fonte
Se você insistir em ser inteligente, poderá usar a solução de Winden e uma inteligência um pouco espúria:
fonte
Já existem algumas soluções aqui, mas outra maneira de fazer isso seria -
Repete a lista apenas uma vez e parece um pouco mais pitônico e, portanto, legível para mim.
fonte
Eu adotaria uma abordagem de 2 passagens, separando a avaliação do predicado da filtragem da lista:
O que é interessante sobre isso, em termos de desempenho (além de avaliar
pred
apenas uma vez em cada membroiterable
), é que ele move muita lógica do interpretador para um código de mapeamento e iteração altamente otimizado. Isso pode acelerar a iteração em iteráveis longos, conforme descrito nesta resposta .Em termos de expressividade, tira proveito de expressões expressivas, como compreensão e mapeamento.
fonte
solução
teste
fonte
Se você não se importa em usar uma biblioteca externa, há duas que sei que implementam nativamente essa operação:
iteration_utilities.partition
:more_itertools.partition
fonte
Não tenho certeza se essa é uma boa abordagem, mas também pode ser feita dessa maneira
fonte
Se a lista for feita de grupos e separadores intermitentes, você poderá usar:
Uso:
fonte
Bom quando a condição é mais longa, como no seu exemplo. O leitor não precisa descobrir a condição negativa e se captura todos os outros casos.
fonte
Mais uma resposta, curta, mas "má" (para efeitos colaterais da compreensão da lista).
fonte