Python: dividere un elenco in base a una condizione?


272

Qual è il modo migliore, sia esteticamente che dal punto di vista delle prestazioni, di dividere un elenco di elementi in più elenchi basati su un condizionale? L'equivalente di:

good = [x for x in mylist if x in goodvals]
bad  = [x for x in mylist if x not in goodvals]

c'è un modo più elegante per farlo?

Aggiornamento: ecco il caso d'uso reale, per spiegare meglio cosa sto cercando di fare:

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

5
atterrato qui alla ricerca di un modo per avere una condizione nella dichiarazione del costruttore del set, la tua domanda ha risposto alla mia domanda :)
Anuvrat Parashar

5
split è una sfortunata descrizione di questa operazione, poiché ha già un significato specifico rispetto alle stringhe di Python. Penso che dividere sia una parola più precisa (o almeno meno sovraccaricata nel contesto degli iterabili di Python) per descrivere questa operazione. Sono atterrato qui alla ricerca di un elenco equivalente di str.split(), per dividere l'elenco in una raccolta ordinata di elenchi secondari consecutivi. Ad esempio split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6]), invece di dividere gli elementi di un elenco per categoria.
Spezzatino del

Discussione sullo stesso argomento su Python-list.
Xiong Chiamiov,

IMAGE_TYPES dovrebbe essere un insieme, invece di una tupla: IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (1) invece di n (o / 2), con praticamente nessuna differenza nella leggibilità.
ChaimG

Risposte:


110
good = [x for x in mylist if x in goodvals]
bad  = [x for x in mylist if x not in goodvals]

c'è un modo più elegante per farlo?

Quel codice è perfettamente leggibile ed estremamente chiaro!

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

Ancora una volta, va bene!

Potrebbero esserci lievi miglioramenti delle prestazioni usando i set, ma è una banale differenza, e trovo che la comprensione dell'elenco sia molto più facile da leggere e non devi preoccuparti che l'ordine venga incasinato, i duplicati vengano rimossi così via.

In effetti, potrei fare un altro passo "all'indietro" e usare un semplice ciclo per:

images, anims = [], []

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

La comprensione di un elenco o l'utilizzo set()va bene fino a quando non è necessario aggiungere qualche altro controllo o un altro po 'di logica - supponiamo che tu voglia rimuovere tutti i jpeg di 0 byte, aggiungi semplicemente qualcosa come ..

if f[1] == 0:
    continue

44
Non esiste un modo per comprendere l'elenco senza dover scorrere l'elenco due volte?
Balki,

35
Il problema è che ciò viola il principio DRY. Sarebbe bello se ci fosse un modo migliore per farlo.
Antimonio

21
Una volta aumentato l'appetito per la programmazione funzionale (Haskell) o lo stile funzionale (LINQ), iniziamo a sentire l'odore di Python per la sua età - [x for x in blah if ...]- verboso, lambdaè goffo e limitato ... Sembra di guidare l'auto più bella dal 1995 ad oggi. Non è lo stesso di allora.
Tomasz Gandor,

6
@TomaszGandor FTR, Haskell è più vecchio di Python (e ne ha influenzato il design). Penso che la sintassi per la comprensione dell'elenco e lambda sia stata deliberatamente mantenuta un po 'troppo prolissa, forse per scoraggiare il loro uso eccessivo. Il che è davvero un po 'un rischio ... per quanto mi piaccia Haskell, posso vedere perché molte persone trovano Python generalmente più leggibile.
leftaroundabout

4
il semplice ciclo è il modo migliore per farlo ... un singolo ciclo, molto chiaro e leggibile
Anentropico

217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)

14
È incredibilmente geniale! Mi ci è voluto un po 'di tempo per capire cosa stesse succedendo. Vorrei sapere se gli altri pensano che questo possa essere considerato un codice leggibile o meno.
jgpaiva,

171
good.append(x) if x in goodvals else bad.append(x)è più leggibile.
dansalmo,

21
@dansalmo Tanto più che si può fare una battuta con la for-ciclo, e se si voleva per aggiungere qualcosa di più complicato di quanto xsi può fare in una appendsola:for x in mylist: (good if isgood(x) else bad).append(x)
yo'

2
@MLister, in tal caso dovresti probabilmente includere la ricerca degli attributi(bad.append, good.append)
John La Rooy,

11
Una variazione leggermente più breve:(good if x in goodvals else bad).append(x)
Pi Delport,

104

Ecco l'approccio dell'iteratore pigro:

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)

Valuta la condizione una volta per articolo e restituisce due generatori, ottenendo prima valori dalla sequenza in cui la condizione è vera, l'altra in cui è falsa.

Perché è pigro puoi usarlo su qualsiasi iteratore, anche 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)))

Di solito però l'approccio di ritorno all'elenco non pigro è migliore:

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

Modifica: per il tuo caso d'uso più specifico di suddividere gli elementi in elenchi diversi per un tasto, ecco una funzione generica che lo fa:

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

Probabilmente hai ragione nel dire che ciò viola il principio YAGNI. Si basa sul presupposto che il numero di elenchi diversi in cui è possibile partizionare le cose crescerà in futuro.
Ants Aasma

17
Potrebbe essere un sacco di codice, ma se [ x for x in my_list if ExpensiveOperation(x) ]impiega molto tempo per essere eseguito, sicuramente non vuoi farlo due volte!
dash-tom-bang,

1
+1 per l'offerta di più varianti tra cui basata su iteratore e una soluzione specifica "in X". L'OP "in buone condizioni" potrebbe essere piccolo, ma sostituirlo con un dizionario molto ampio o un predicato costoso potrebbe essere costoso. Inoltre riduce la necessità di scrivere due volte la comprensione dell'elenco ovunque sia necessaria, riducendo così la probabilità di introdurre errori di battitura / utente. Bella soluzione. Grazie!
cod3monk3y,

3
Nota che teememorizza tutti i valori tra gli iteratori che restituisce, quindi non risparmierà davvero memoria se esegui il ciclo su un intero generatore e poi sull'altro.
John La Rooy,

25

Il problema con tutte le soluzioni proposte è che scansionerà e applicherà la funzione di filtro due volte. Farei una semplice piccola funzione come questa:

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

In questo modo non stai elaborando nulla due volte e inoltre non stai ripetendo il codice.


Sono d'accordo. Stavo cercando un modo "elegante" (cioè qui che significa breve e integrato / implicito) per farlo senza scansionare l'elenco due volte, ma questo sembra (senza profilazione) essere la strada da percorrere. Ovviamente sarebbe importante solo per grandi quantità di dati.
Matthew Flaschen,

IMHO, se conosci un modo di farlo con meno utilizzo della CPU (e quindi meno consumo di energia), non c'è motivo di non usarlo.
Carica il

2
@winden ... Porting tutto il mio Python su C.;)
Elliot Cameron

19

La mia opinione su di esso. Propongo una funzione pigra, a passaggio singolo partition, che conserva l'ordine relativo nelle sottosequenze di output.

1. Requisiti

Presumo che i requisiti siano:

  • mantenere l'ordine relativo degli elementi (quindi, nessun set e dizionari)
  • valutare la condizione una sola volta per ogni elemento (quindi non usare ( i) filtero groupby)
  • consentire un consumo pigro di entrambe le sequenze (se possiamo permetterci di pre-calcolarle, è probabile che anche l'implementazione ingenua sia accettabile)

2. splitbiblioteca

La mia partitionfunzione (introdotta di seguito) e altre funzioni simili l'hanno trasformata in una piccola libreria:

È installabile normalmente tramite PyPI:

pip install --user split

Per dividere un elenco in base alla condizione, utilizzare la partitionfunzione:

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

Internamente dobbiamo costruire due sottosequenze contemporaneamente, quindi consumare solo una sequenza di output costringerà anche l'altra a essere calcolata. E dobbiamo mantenere lo stato tra le richieste degli utenti (archivio elaborato ma elementi non ancora richiesti). Per mantenere lo stato, uso due code a doppia estremità ( deques):

from collections import deque

SplitSeq la classe si occupa delle pulizie:

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

La magia accade nel suo .getNext()metodo. È quasi come .next() gli iteratori, ma permette di specificare quale tipo di elemento vogliamo questa volta. Dietro la scena non scarta gli elementi rifiutati, ma li inserisce in una delle due code:

    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)

L'utente finale dovrebbe usare la partitionfunzione. Prende una funzione condizione e una sequenza (proprio come mapo filter) e restituisce due generatori. Il primo generatore crea una sottosequenza di elementi per cui la condizione è valida, il secondo crea la sottosequenza complementare. Iteratori e generatori consentono la divisione pigra di sequenze anche lunghe o infinite.

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

Ho scelto la funzione di test come primo argomento per facilitare l'applicazione parziale in futuro (simile a come mape filter avere la funzione di test come primo argomento).


15

Fondamentalmente mi piace l'approccio di Anders in quanto è molto generale. Ecco una versione che mette per primo il classificatore (in modo che corrisponda alla sintassi del filtro) e utilizza un valore predefinito (presunto importato).

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

Stavo per provare a scegliere le dichiarazioni di Zen of Python che si applicano qui, ma sono troppe per un commento. =) Fantastico pezzo di codice.
jpmc26,

13

Primo passaggio (modifica pre-OP): utilizzare i set:

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]

Va bene sia per la leggibilità (IMHO) che per le prestazioni.

Secondo passaggio (post-modifica OP):

Crea il tuo elenco di buone estensioni come set:

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

e ciò aumenterà le prestazioni. Altrimenti, quello che hai mi sembra perfetto.


4
non è la soluzione migliore se gli elenchi fossero in un certo ordine prima di dividere e hai bisogno che rimangano in quell'ordine.
Daniyar,

8
Non rimuoverebbe i duplicati?
Mavnn,

La creazione di un set è O (n log n). Iterare l'elenco due volte è O (n). La soluzione impostata può essere più elegante (quando è corretta in primo luogo) ma è sicuramente più lenta all'aumentare di n.
dash-tom-bang

1
@ dash-tom-bang Iterazione dell'elenco è O (n * n). Questo perché potrebbe essere necessario confrontare ogni elemento nell'elenco goodvals.
ChaimG

@ChaimG buon punto, anche se dobbiamo anche considerare il costo delle operazioni di intersezione e differenza (che non conosco fuori mano ma sono abbastanza sicuro che siano anche superlineari).
dash-tom-bang,

10

itertools.groupby fa quasi quello che vuoi, tranne per il fatto che gli elementi devono essere ordinati per assicurarti di ottenere un singolo intervallo contiguo, quindi devi prima ordinare in base alla tua chiave (altrimenti otterrai più gruppi interfogliati per ogni tipo). per esempio.

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

Simile alle altre soluzioni, la funzione chiave può essere definita per dividere in qualsiasi numero di gruppi desiderati.


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

Questa risposta elegante e concisa di @dansalmo si è rivelata sepolta nei commenti, quindi la ripubblico qui come una risposta in modo che possa ottenere l'importanza che merita, soprattutto per i nuovi lettori.

Esempio completo:

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

5

Se vuoi farlo in stile FP:

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

Non è la soluzione più leggibile, ma almeno scorre la mia lista solo una volta.


1
Anche se scorre l'elenco solo una volta, le prestazioni non sono buone a causa dell'aggiunta dell'elenco. L'aggiunta a un elenco è un'operazione potenzialmente costosa (rispetto ad esempio a deque.append). In realtà, questa soluzione è estremamente lenta se confrontata con altre soluzioni qui (21.4s su 100000 numeri interi casuali e testando il loro valore).
dal

5

Personalmente, mi piace la versione che hai citato, supponendo che tu abbia già un elenco di in goodvalsgiro. Altrimenti, qualcosa del tipo:

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

Certo, è davvero molto simile all'utilizzo di una comprensione dell'elenco come facevi in ​​origine, ma con una funzione anziché una ricerca:

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

In generale, trovo molto piacevole l'estetica della comprensione delle liste. Naturalmente, se in realtà non hai bisogno di preservare l'ordinamento e non hai bisogno di duplicati, usare anche i metodi intersectione differencesui set funzionerebbe bene.


Certo, filter(lambda x: is_good(x), mylist)può essere ridotto afilter(is_good, mylist)
robru il

l'aggiunta della chiamata di funzione extra raddoppia (!) il tempo di esecuzione, rispetto alle comprensioni dell'elenco, da quello che ho visto nella profilazione. è difficile battere la comprensione di un elenco, il più delle volte.
Corley Brigman,

4

Penso che una generalizzazione della divisione di un iterabile in base alle condizioni N sia utile

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

Per esempio:

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 l'elemento può soddisfare più condizioni, rimuovere l'interruzione.


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)

Controlla questo


3

A volte, sembra che la comprensione dell'elenco non sia la cosa migliore da usare!

Ho fatto un piccolo test basato sulla risposta che le persone hanno dato a questo argomento, testato su un elenco generato casualmente. Ecco la generazione dell'elenco (probabilmente c'è un modo migliore per farlo, ma non è questo il punto):

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

Ed eccoci qui

# 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 la funzione cmpthese , il risultato migliore è la risposta 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%  --

Funzioni più veloci con benchmark aggiornati qui .
ChaimG

2

Ancora un'altra soluzione a questo problema. Avevo bisogno di una soluzione il più veloce possibile. Ciò significa solo una iterazione sull'elenco e preferibilmente O (1) per l'aggiunta di dati a uno degli elenchi risultanti. Questo è molto simile alla soluzione fornita dalla sastanina , tranne che molto più breve:

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

Quindi, è possibile utilizzare la funzione nel modo seguente:

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 non stai bene con l' dequeoggetto risultante , puoi facilmente convertirlo in list, setcome preferisci (ad esempio list(lower)). La conversione è molto più veloce, quella costruzione delle liste direttamente.

Questo metodo mantiene l'ordine degli articoli, nonché eventuali duplicati.


2

Ad esempio, dividere l'elenco per pari e dispari

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

O in generale:

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

vantaggi:

  • Il modo più breve possibile
  • Il predicato si applica una sola volta per ogni elemento

svantaggi

  • Richiede la conoscenza del paradigma di programmazione funzionale

2

Ispirati dalla grande (ma concisa!) Risposta di @ gnibbler , possiamo applicare questo approccio per mappare a più partizioni:

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

Quindi splitterpuò essere utilizzato come segue:

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

Funziona con più di due partizioni con una mappatura più complicata (e anche su iteratori):

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

O usando un dizionario per mappare:

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

... ho appena notato che questo è sostanzialmente lo stesso di cui ha già risposto @ alan-isaac.
Josh Bode,

2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

append restituisce None, quindi funziona.


1

Per prestazioni, prova itertools.

Il modulo itertools standardizza una serie di strumenti veloci, efficienti in termini di memoria, utili da soli o in combinazione. Insieme, formano una "algebra iteratrice" che consente di costruire strumenti specializzati in modo succinto ed efficiente in puro Python.

Vedi itertools.ifilter o imap.

itertools.ifilter (predicato, iterabile)

Crea un iteratore che filtra gli elementi da iterabile restituendo solo quelli per i quali il predicato è True


ifilter / imap (e generatori in generale) sono piuttosto lenti ... in generale, nel mio profilo, se prendi una comprensione dell'elenco come [x for x in a if x > 50000]su un semplice array di 100000 numeri interi (tramite random.shuffle), filter(lambda x: x> 50000, a)impiegherà il doppio del tempo, ifilter(lambda x: x> 50000, a); list(result)impiega circa 2,3 volte più a lungo. Strano ma vero.
Corley Brigman,

1

A volte non avrai bisogno dell'altra metà dell'elenco. Per esempio:

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

1

Questo è il modo più veloce.

Usa if else(come la risposta di dbr) ma crea prima un set. Un set riduce il numero di operazioni da O (m * n) a O (log m) + O (n), con un conseguente aumento del 45% + della velocità.

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)

Un po 'più corto:

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)

Risultati benchmark:

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

Il codice di riferimento completo per Python 3.7 (modificato da 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(" "))

0

Se insisti sull'intelligenza, potresti prendere la soluzione di Winden e un'intelligenza un po 'falsa:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d

3
"D or {}" è un po 'pericoloso. Se un dict vuoto viene superato, non verrà modificato in posizione.
Brian,

È vero, ma viene restituito, quindi ... In realtà, questo è l'esempio perfetto del motivo per cui non vuoi aggiungere più intelligenza al tuo codice. :-P
Anders Eurenius,

0

Già alcune soluzioni qui, ma un altro modo per farlo sarebbe:

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

Scorre l'elenco una volta sola e sembra un po 'più pitonico e quindi leggibile per me.

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

0

Prenderei un approccio a 2 passaggi, separando la valutazione del predicato dal filtraggio dell'elenco:

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

La cosa bella di questo, dal punto di vista delle prestazioni (oltre a valutare predsolo una volta su ciascun membro di iterable), è che sposta molta logica fuori dall'interprete e si trasforma in codice di iterazione e mappatura altamente ottimizzato. Questo può accelerare l'iterazione su iterabili lunghi, come descritto in questa risposta .

Per quanto riguarda l'espressività, sfrutta idiomi espressivi come la comprensione e la mappatura.


0

soluzione

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

test

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

0

Se non ti dispiace usare una libreria esterna ce ne sono due che so che implementare nativamente questa operazione:

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

0

Non sono sicuro che questo sia un buon approccio, ma può essere fatto anche in questo modo

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, ([], []))

0

Se l'elenco è composto da gruppi e separatori intermittenti, è possibile utilizzare:

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

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]

Bello quando la condizione è più lunga, come nel tuo esempio. Il lettore non deve capire la condizione negativa e se rileva tutti gli altri casi.


0

Ancora un'altra risposta, breve ma "malvagia" (per gli effetti collaterali di comprensione dell'elenco).

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]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.