Python: dividir uma lista com base em uma condição?

272

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]
Parand
fonte
5
desembarcaram aqui à procura de uma maneira de ter uma condição na declaração de construtor de jogo, a sua pergunta respondeu à minha pergunta :)
Anuvrat Parashar
5
split é uma descrição infeliz dessa operação, já que ela já possui um significado específico em relação às seqüências de caracteres Python. Eu acho que dividir é uma palavra mais precisa (ou pelo menos menos sobrecarregada no contexto das iteráveis ​​do Python) para descrever essa operação. Cheguei aqui procurando uma lista equivalente a str.split(), para dividir a lista em uma coleção ordenada de sub-listas consecutivas. Por exemplo split([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.
Guisado
Discussão sobre o mesmo tópico na lista python.
Xiong Chiamiov 10/10
IMAGE_TYPES deve ser um conjunto em vez de uma tupla: IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (1) em vez de n (o / 2), praticamente sem diferença na legibilidade.
ChaimG

Respostas:

110
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?

Esse código é perfeitamente legível e extremamente claro!

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

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:

images, anims = [], []

for f in files:
    if f.lower() in IMAGE_TYPES:
        images.append(f)
    else:
        anims.append(f)

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

if f[1] == 0:
    continue
dbr
fonte
44
Não existe uma maneira de compreender a lista sem ter que percorrer a lista duas vezes?
balki
35
O problema é que isso viola o princípio DRY. Seria bom se houvesse uma maneira melhor de fazer isso.
Antimony
21
Quando o apetite por programação funcional (Haskell), ou estilo funcional (LINQ) aumenta, começamos a sentir o cheiro do Python por sua idade [x for x in blah if ...]- verbosa, lambdadesajeitada e limitada ... Parece como dirigir o carro mais legal de 1995 hoje. Não é o mesmo que naquela época.
Tomasz Gandor
6
@TomaszGandor FTR, Haskell é mais antigo que Python (e realmente influenciou seu design). Eu acho que a sintaxe para compreensão de lista e lambdas foi deliberadamente mantida um pouco do lado detalhado, talvez para desencorajar o uso excessivo deles. O que é realmente um pouco de risco ... por mais que eu goste de Haskell, posso ver por que muitas pessoas acham o Python geralmente mais legível.
usar o seguinte código
4
o loop for simples é a melhor maneira de fazer isso ... um único loop, muito claro e legível
Anentropic
217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)
John La Rooy
fonte
14
Isso é incrivelmente engenhoso! Levei um tempo para entender o que estava acontecendo. Gostaria de saber se outras pessoas acham que isso pode ser considerado código legível ou não.
Jgpaiva
171
good.append(x) if x in goodvals else bad.append(x)é mais legível.
Dansalmo 30/05
21
@dansalmo Especialmente porque você pode torná-lo uma linha com o ciclo for, e se você quiser acrescentar algo mais complicado do que isso x, você pode transformá-lo em appendapenas um :for x in mylist: (good if isgood(x) else bad).append(x)
yo '
2
@MLister, nesse caso, você provavelmente deve incluir a pesquisa atributo(bad.append, good.append)
John La Rooy
11
Uma variação ligeiramente mais curta:(good if x in goodvals else bad).append(x)
Pi Delport 2/17/17
104

Aqui está a abordagem do iterador lento:

from itertools import tee

def split_on_condition(seq, condition):
    l1, l2 = tee((condition(item), item) for item in seq)
    return (i for p, i in l1 if p), (i for p, i in l2 if not p)

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:

from itertools import count, islice

def is_prime(n):
    return n > 1 and all(n % i for i in xrange(2, n))

primes, not_primes = split_on_condition(count(), is_prime)
print("First 10 primes", list(islice(primes, 10)))
print("First 10 non-primes", list(islice(not_primes, 10)))

Geralmente, embora a abordagem de retorno de lista não lenta seja melhor:

def split_on_condition(seq, condition):
    a, b = [], []
    for item in seq:
        (a if condition(item) else b).append(item)
    return a, b

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:

DROP_VALUE = lambda _:_
def split_by_key(seq, resultmapping, keyfunc, default=DROP_VALUE):
    """Split a sequence into lists based on a key function.

        seq - input sequence
        resultmapping - a dictionary that maps from target lists to keys that go to that list
        keyfunc - function to calculate the key of an input value
        default - the target where items that don't have a corresponding key go, by default they are dropped
    """
    result_lists = dict((key, []) for key in resultmapping)
    appenders = dict((key, result_lists[target].append) for target, keys in resultmapping.items() for key in keys)

    if default is not DROP_VALUE:
        result_lists.setdefault(default, [])
        default_action = result_lists[default].append
    else:
        default_action = DROP_VALUE

    for item in seq:
        appenders.get(keyfunc(item), default_action)(item)

    return result_lists

Uso:

def file_extension(f):
    return f[2].lower()

split_files = split_by_key(files, {'images': IMAGE_TYPES}, keyfunc=file_extension, default='anims')
print split_files['images']
print split_files['anims']
Formigas Aasma
fonte
Você provavelmente está certo de que isso viola o princípio YAGNI. É baseado no pressuposto de que o número de listas diferentes nas quais as coisas podem ser particionadas crescerá no futuro.
Ants Aasma #: 0630
17
Pode ser muito código, mas se [ x for x in my_list if ExpensiveOperation(x) ]levar muito tempo para ser executado, você certamente não deseja fazer isso duas vezes!
traço-tom-bang
1
+1 por oferecer várias variações, incluindo uma solução "in X" baseada em iterador. O PO "em boas condições" pode ser pequeno, mas substituí-lo por um dicionário muito grande ou um predicado caro pode ser caro. Além disso, reduz a necessidade de escrever a compreensão da lista duas vezes em todos os lugares necessários, reduzindo assim a probabilidade de introdução de erros de digitação / erro do usuário. Ótima solução. Obrigado!
Cod3monk3y
3
Observe que teearmazena 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.
John La Rooy
25

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:

def SplitIntoTwoLists(l, f):
  a = []
  b = []
  for i in l:
    if f(i):
      a.append(i)
    else:
      b.append(i)
 return (a,b)

Dessa forma, você não está processando nada duas vezes e também não está repetindo o código.

winden
fonte
Concordo. Eu estava procurando por uma maneira "elegante" (isto é, aqui, curta e embutida / implícita) de fazer isso sem escanear a lista duas vezes, mas isso parece (sem criação de perfil) o caminho a seguir. É claro que isso só importaria para grandes quantidades de dados.
Matthew Flaschen
IMHO, se você conhece uma maneira de fazê-lo com menos uso da CPU (e, portanto, menos consumo de energia), não há razão para não usá-lo.
winden
2
@winden ... Portando todo o meu Python para C.;)
Elliot Cameron
19

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:

  • manter a ordem relativa dos elementos (portanto, sem conjuntos e dicionários)
  • avalie a condição apenas uma vez para cada elemento (portanto, não use ( i) filterou groupby)
  • permitir o consumo preguiçoso de qualquer uma das seqüências (se pudermos pré-calculá-las, é provável que a implementação ingênua também seja aceitável)

2. splitbiblioteca

Minha partitionfunção (apresentada abaixo) e outras funções semelhantes a transformaram em uma pequena biblioteca:

É instalável normalmente via PyPI:

pip install --user split

Para dividir uma lista com base na condição, use a partitionfunção:

>>> from split import partition
>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi') ]
>>> image_types = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> images, other = partition(lambda f: f[-1] in image_types, files)
>>> list(images)
[('file1.jpg', 33L, '.jpg')]
>>> list(other)
[('file2.avi', 999L, '.avi')]

3. partitionfunção explicada

Internamente, 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):

from collections import deque

SplitSeq A classe cuida da limpeza:

class SplitSeq:
    def __init__(self, condition, sequence):
        self.cond = condition
        self.goods = deque([])
        self.bads = deque([])
        self.seq = iter(sequence)

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:

    def getNext(self, getGood=True):
        if getGood:
            these, those, cond = self.goods, self.bads, self.cond
        else:
            these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
        if these:
            return these.popleft()
        else:
            while 1: # exit on StopIteration
                n = self.seq.next()
                if cond(n):
                    return n
                else:
                    those.append(n)

O usuário final deve usar a partitionfunção Ele pega uma função de condição e uma sequência (exatamente como mapou filter) 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.

def partition(condition, sequence):
    cond = condition if condition else bool  # evaluate as bool if condition == None
    ss = SplitSeq(cond, sequence)
    def goods():
        while 1:
            yield ss.getNext(getGood=True)
    def bads():
        while 1:
            yield ss.getNext(getGood=False)
    return goods(), bads()

Eu escolhi a função de teste como o primeiro argumento para facilitar a aplicação parcial no futuro (semelhante a como mape filter ter a função de teste como o primeiro argumento).

sastanina
fonte
15

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

def categorize(func, seq):
    """Return mapping from categories to lists
    of categorized items.
    """
    d = defaultdict(list)
    for item in seq:
        d[func(item)].append(item)
    return d
Alan Isaac
fonte
Eu tentaria escolher as declarações do Zen of Python que se aplicam aqui, mas são muitas para um comentário. =) Trecho impressionante de código.
jpmc26
13

Primeiro acesso (edição pré-OP): use conjuntos:

mylist = [1,2,3,4,5,6,7]
goodvals = [1,3,7,8,9]

myset = set(mylist)
goodset = set(goodvals)

print list(myset.intersection(goodset))  # [1, 3, 7]
print list(myset.difference(goodset))    # [2, 4, 5, 6]

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:

IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])

e isso aumentará o desempenho. Caso contrário, o que você tem parece bom para mim.

RichieHindle
fonte
4
Não é a melhor solução se as listas estiverem em alguma ordem antes da divisão e você precisar que elas permaneçam nessa ordem.
Daniyar
8
Isso não removeria duplicatas?
mavnn
Criar um conjunto é O (n log n). Iterar a lista duas vezes é O (n). A solução definida pode ser mais elegante (quando estiver correta em primeiro lugar), mas certamente é mais lenta à medida que n aumenta.
traço-tom-bang
1
@ dash-tom-bang A iteração da lista é O (n * n). Isso porque cada item da lista pode precisar ser comparado com cada item goodvals.
ChaimG
@ChaimG é bom, apesar de também precisarmos considerar o custo das operações de interseção e diferença (que eu não sei de antemão, mas tenho certeza de que também são superlineares).
dash-tom-bang
10

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.

def is_good(f):
    return f[2].lower() in IMAGE_TYPES

files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file3.gif', 123L, '.gif')]

for key, group in itertools.groupby(sorted(files, key=is_good), key=is_good):
    print key, list(group)

dá:

False [('file2.avi', 999L, '.avi')]
True [('file1.jpg', 33L, '.jpg'), ('file3.gif', 123L, '.gif')]

Semelhante às outras soluções, a tecla func pode ser definida para se dividir em qualquer número de grupos que você desejar.

Brian
fonte
6
good.append(x) if x in goodvals else bad.append(x)

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:

good, bad = [], []
for x in my_list:
    good.append(x) if x in goodvals else bad.append(x)
John D
fonte
5

Se você quiser fazer isso no estilo FP:

good, bad = [ sum(x, []) for x in zip(*(([y], []) if y in goodvals else ([], [y])
                                        for y in mylist)) ]

Não é a solução mais legível, mas pelo menos itera a lista apenas uma vez.

michau
fonte
1
Embora itere pela lista apenas uma vez, o desempenho não é tão bom por causa da lista anexada. Anexar a uma lista é uma operação potencialmente cara (quando comparada com deque.append, por exemplo). Na verdade, essa solução é extremamente lenta quando comparada com outras soluções aqui (21,4s em 100000 números inteiros aleatórios e testando seu valor).
rlat
5

Pessoalmente, eu gosto da versão que você citou, supondo que você já tenha uma lista goodvalspor aí. Caso contrário, algo como:

good = filter(lambda x: is_good(x), mylist)
bad = filter(lambda x: not is_good(x), mylist)

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:

good = [x for x in mylist if is_good(x)]
bad  = [x for x in mylist if not is_good(x)]

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 intersectione differencenos conjuntos também funcionaria bem.

BJ Homer
fonte
Claro, filter(lambda x: is_good(x), mylist)pode ser reduzido afilter(is_good, mylist)
robru
adicionar a chamada de função extra na verdade dobra (!) o tempo de execução, comparado com as compreensões da lista, do que eu vi no perfil. é difícil superar a compreensão de uma lista, na maioria das vezes.
Corley Brigman
4

Eu acho que uma generalização de dividir um iterável com base em N condições é útil

from collections import OrderedDict
def partition(iterable,*conditions):
    '''Returns a list with the elements that satisfy each of condition.
       Conditions are assumed to be exclusive'''
    d= OrderedDict((i,list())for i in range(len(conditions)))        
    for e in iterable:
        for i,condition in enumerate(conditions):
            if condition(e):
                d[i].append(e)
                break                    
    return d.values()

Por exemplo:

ints,floats,other = partition([2, 3.14, 1, 1.69, [], None],
                              lambda x: isinstance(x, int), 
                              lambda x: isinstance(x, float),
                              lambda x: True)

print " ints: {}\n floats:{}\n other:{}".format(ints,floats,other)

 ints: [2, 1]
 floats:[3.14, 1.69]
 other:[[], None]

Se o elemento puder atender a várias condições, remova a interrupção.

Lagartixa
fonte
3
def partition(pred, iterable):
    'Use a predicate to partition entries into false entries and true entries'
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)

Verifique isto

Hanfei Sun
fonte
3

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

good_list = ('.jpg','.jpeg','.gif','.bmp','.png')

import random
import string
my_origin_list = []
for i in xrange(10000):
    fname = ''.join(random.choice(string.lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(good_list)
    else:
        fext = "." + ''.join(random.choice(string.lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

E aqui vamos nós

# Parand
def f1():
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def f2():
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def f3():
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# Ants Aasma
def f4():
    l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
    return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def f5():
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def f6():
    return filter(lambda e: e[2] in good_list, my_origin_list), filter(lambda e: not e[2] in good_list, my_origin_list)

Usando a função cmpthese , o melhor resultado é a resposta dbr:

f1     204/s  --    -5%   -14%   -15%   -20%   -26%
f6     215/s     6%  --    -9%   -11%   -16%   -22%
f3     237/s    16%    10%  --    -2%    -7%   -14%
f4     240/s    18%    12%     2%  --    -6%   -13%
f5     255/s    25%    18%     8%     6%  --    -8%
f2     277/s    36%    29%    17%    15%     9%  --
FunkySayu
fonte
Funções mais rápidas com benchmarks atualizados aqui .
ChaimG
2

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:

from collections import deque

def split(iterable, function):
    dq_true = deque()
    dq_false = deque()

    # deque - the fastest way to consume an iterator and append items
    deque((
      (dq_true if function(item) else dq_false).append(item) for item in iterable
    ), maxlen=0)

    return dq_true, dq_false

Em seguida, você pode usar a função da seguinte maneira:

lower, higher = split([0,1,2,3,4,5,6,7,8,9], lambda x: x < 5)

selected, other = split([0,1,2,3,4,5,6,7,8,9], lambda x: x in {0,4,9})

Se você não está bem com o resultado dequeobjeto, você pode facilmente convertê-lo para list, set, o que você como (por exemplo list(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.

rlat
fonte
2

Por exemplo, dividir a lista por pares e ímpares

arr = range(20)
even, odd = reduce(lambda res, next: res[next % 2].append(next) or res, arr, ([], []))

Ou em geral:

def split(predicate, iterable):
    return reduce(lambda res, e: res[predicate(e)].append(e) or res, iterable, ([], []))

Vantagens:

  • Maneira mais curta possível
  • Predicado se aplica apenas uma vez para cada elemento

Desvantagens

  • Requer conhecimento do paradigma de programação funcional
Pavel Ilchenko
fonte
2

Inspirados na ótima resposta (mas concisa!) Do @ gnibbler , podemos aplicar essa abordagem ao mapeamento para várias partições:

from collections import defaultdict

def splitter(l, mapper):
    """Split an iterable into multiple partitions generated by a callable mapper."""

    results = defaultdict(list)

    for x in l:
        results[mapper(x)] += [x]

    return results

Em seguida, splitterpode ser usado da seguinte maneira:

>>> l = [1, 2, 3, 4, 2, 3, 4, 5, 6, 4, 3, 2, 3]
>>> split = splitter(l, lambda x: x % 2 == 0)  # partition l into odds and evens
>>> split.items()
>>> [(False, [1, 3, 3, 5, 3, 3]), (True, [2, 4, 2, 4, 6, 4, 2])]

Isso funciona para mais de duas partições com um mapeamento mais complicado (e também nos iteradores):

>>> import math
>>> l = xrange(1, 23)
>>> split = splitter(l, lambda x: int(math.log10(x) * 5))
>>> split.items()
[(0, [1]),
 (1, [2]),
 (2, [3]),
 (3, [4, 5, 6]),
 (4, [7, 8, 9]),
 (5, [10, 11, 12, 13, 14, 15]),
 (6, [16, 17, 18, 19, 20, 21, 22])]

Ou usando um dicionário para mapear:

>>> map = {'A': 1, 'X': 2, 'B': 3, 'Y': 1, 'C': 2, 'Z': 3}
>>> l = ['A', 'B', 'C', 'C', 'X', 'Y', 'Z', 'A', 'Z']
>>> split = splitter(l, map.get)
>>> split.items()
(1, ['A', 'Y', 'A']), (2, ['C', 'C', 'X']), (3, ['B', 'Z', 'Z'])]
Josh Bode
fonte
... acabei de notar que isso é basicamente o mesmo que @ alan-isaac já respondeu.
precisa
2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

append retorna None, então funciona.

Biga
fonte
1

Para o desempenho, tente itertools.

O módulo itertools padroniza um conjunto principal de ferramentas rápidas e eficientes em termos de memória, úteis por si só ou em combinação. Juntos, eles formam uma “álgebra iteradora”, possibilitando a construção de ferramentas especializadas de maneira sucinta e eficiente em Python puro.

Veja itertools.ifilter ou imap.

itertools.ifilter (predicado, iterável)

Crie um iterador que filtre elementos de iterável retornando apenas aqueles para os quais o predicado é True

gimel
fonte
ifilter / imap (e geradores em geral) são bem lentos ... em geral, no meu perfil, se você entender uma lista como [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.
Corley Brigman
1

Às vezes você não precisará dessa outra metade da lista. Por exemplo:

import sys
from itertools import ifilter

trustedPeople = sys.argv[1].split(',')
newName = sys.argv[2]

myFriends = ifilter(lambda x: x.startswith('Shi'), trustedPeople)

print '%s is %smy friend.' % (newName, newName not in myFriends 'not ' or '')
Shikhar Mall
fonte
1

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.

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    if item in good_list_set:
        good.append(item)
    else:
        bad.append(item)

Um pouco mais curto:

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    out = good if item in good_list_set else bad
    out.append(item)

Resultados de referência:

filter_BJHomer                  80/s       --   -3265%   -5312%   -5900%   -6262%   -7273%   -7363%   -8051%   -8162%   -8244%
zip_Funky                       118/s    4848%       --   -3040%   -3913%   -4450%   -5951%   -6085%   -7106%   -7271%   -7393%
two_lst_tuple_JohnLaRoy         170/s   11332%    4367%       --   -1254%   -2026%   -4182%   -4375%   -5842%   -6079%   -6254%
if_else_DBR                     195/s   14392%    6428%    1434%       --    -882%   -3348%   -3568%   -5246%   -5516%   -5717%
two_lst_compr_Parand            213/s   16750%    8016%    2540%     967%       --   -2705%   -2946%   -4786%   -5083%   -5303%
if_else_1_line_DanSalmo         292/s   26668%   14696%    7189%    5033%    3707%       --    -331%   -2853%   -3260%   -3562%
tuple_if_else                   302/s   27923%   15542%    7778%    5548%    4177%     343%       --   -2609%   -3029%   -3341%
set_1_line                      409/s   41308%   24556%   14053%   11035%    9181%    3993%    3529%       --    -569%    -991%
set_shorter                     434/s   44401%   26640%   15503%   12303%   10337%    4836%    4345%     603%       --    -448%
set_if_else                     454/s   46952%   28358%   16699%   13349%   11290%    5532%    5018%    1100%     469%       --

O código de referência completo para Python 3.7 (modificado a partir FunkySayu):

good_list = ['.jpg','.jpeg','.gif','.bmp','.png']

import random
import string
my_origin_list = []
for i in range(10000):
    fname = ''.join(random.choice(string.ascii_lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(list(good_list))
    else:
        fext = "." + ''.join(random.choice(string.ascii_lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

# Parand
def two_lst_compr_Parand(*_):
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def if_else_DBR(*_):
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def two_lst_tuple_JohnLaRoy(*_):
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# # Ants Aasma
# def f4():
#     l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
#     return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def zip_Funky(*_):
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def filter_BJHomer(*_):
    return list(filter(lambda e: e[2] in good_list, my_origin_list)), list(filter(lambda e: not e[2] in good_list,                                                                             my_origin_list))

# ChaimG's answer; as a list.
def if_else_1_line_DanSalmo(*_):
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list else bad.append(e)
    return good, bad

# ChaimG's answer; as a set.
def set_1_line(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list_set else bad.append(e)
    return good, bad

# ChaimG set and if else list.
def set_shorter(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        out = good if e[2] in good_list_set else bad
        out.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def set_if_else(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_set:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def tuple_if_else(*_):
    good_list_tuple = tuple(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_tuple:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

def cmpthese(n=0, functions=None):
    results = {}
    for func_name in functions:
        args = ['%s(range(256))' % func_name, 'from __main__ import %s' % func_name]
        t = Timer(*args)
        results[func_name] = 1 / (t.timeit(number=n) / n) # passes/sec

    functions_sorted = sorted(functions, key=results.__getitem__)
    for f in functions_sorted:
        diff = []
        for func in functions_sorted:
            if func == f:
                diff.append("--")
            else:
                diff.append(f"{results[f]/results[func]*100 - 100:5.0%}")
        diffs = " ".join(f'{x:>8s}' for x in diff)

        print(f"{f:27s} \t{results[f]:,.0f}/s {diffs}")


if __name__=='__main__':
    from timeit import Timer
cmpthese(1000, 'two_lst_compr_Parand if_else_DBR two_lst_tuple_JohnLaRoy zip_Funky filter_BJHomer if_else_1_line_DanSalmo set_1_line set_if_else tuple_if_else set_shorter'.split(" "))
ChaimG
fonte
0

Se você insistir em ser inteligente, poderá usar a solução de Winden e uma inteligência um pouco espúria:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d
Anders Eurenius
fonte
3
O "d ou {}" é um pouco perigoso. Se um ditado vazio for passado, não será alterado no lugar.
Brian
É verdade, mas é retornado, então ... Na verdade, este é o exemplo perfeito de por que você não deseja adicionar mais inteligência ao seu código. :-P
Anders Eurenius
0

Já existem algumas soluções aqui, mas outra maneira de fazer isso seria -

anims = []
images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]

Repete a lista apenas uma vez e parece um pouco mais pitônico e, portanto, legível para mim.

>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file1.bmp', 33L, '.bmp')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> anims = []
>>> images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]
>>> print '\n'.join([str(anims), str(images)])
[('file2.avi', 999L, '.avi')]
[('file1.jpg', 33L, '.jpg'), ('file1.bmp', 33L, '.bmp')]
>>>
Shreyas
fonte
0

Eu adotaria uma abordagem de 2 passagens, separando a avaliação do predicado da filtragem da lista:

def partition(pred, iterable):
    xs = list(zip(map(pred, iterable), iterable))
    return [x[1] for x in xs if x[0]], [x[1] for x in xs if not x[0]]

O que é interessante sobre isso, em termos de desempenho (além de avaliar predapenas uma vez em cada membro iterable), é 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.

Jim Witschey
fonte
0

solução

from itertools import tee

def unpack_args(fn):
    return lambda t: fn(*t)

def separate(fn, lx):
    return map(
        unpack_args(
            lambda i, ly: filter(
                lambda el: bool(i) == fn(el),
                ly)),
        enumerate(tee(lx, 2)))

teste

[even, odd] = separate(
    lambda x: bool(x % 2),
    [1, 2, 3, 4, 5])
print(list(even) == [2, 4])
print(list(odd) == [1, 3, 5])
doctorzeb8
fonte
0

Se você não se importa em usar uma biblioteca externa, há duas que sei que implementam nativamente essa operação:

>>> files = [ ('file1.jpg', 33, '.jpg'), ('file2.avi', 999, '.avi')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
  • iteration_utilities.partition:

    >>> from iteration_utilities import partition
    >>> notimages, images = partition(files, lambda x: x[2].lower() in IMAGE_TYPES)
    >>> notimages
    [('file2.avi', 999, '.avi')]
    >>> images
    [('file1.jpg', 33, '.jpg')]
  • more_itertools.partition

    >>> from more_itertools import partition
    >>> notimages, images = partition(lambda x: x[2].lower() in IMAGE_TYPES, files)
    >>> list(notimages)  # returns a generator so you need to explicitly convert to list.
    [('file2.avi', 999, '.avi')]
    >>> list(images)
    [('file1.jpg', 33, '.jpg')]
MSeifert
fonte
0

Não tenho certeza se essa é uma boa abordagem, mas também pode ser feita dessa maneira

IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi')]
images, anims = reduce(lambda (i, a), f: (i + [f], a) if f[2] in IMAGE_TYPES else (i, a + [f]), files, ([], []))
Kiran
fonte
0

Se a lista for feita de grupos e separadores intermitentes, você poderá usar:

def split(items, p):
    groups = [[]]
    for i in items:
        if p(i):
            groups.append([])
        groups[-1].append(i)
    return groups

Uso:

split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
David
fonte
0
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f not in images]

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.

Chrisjan
fonte
0

Mais uma resposta, curta, mas "má" (para efeitos colaterais da compreensão da lista).

digits = list(range(10))
odd = [x.pop(i) for i, x in enumerate(digits) if x % 2]

>>> odd
[1, 3, 5, 7, 9]

>>> digits
[0, 2, 4, 6, 8]
Shay
fonte