Python: membagi daftar berdasarkan kondisi?

272

Apa cara terbaik, baik secara estetika dan dari perspektif kinerja, untuk membagi daftar item menjadi beberapa daftar berdasarkan persyaratan? Setara dengan:

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

apakah ada cara yang lebih elegan untuk melakukan ini?

Pembaruan: inilah use case yang sebenarnya, untuk lebih menjelaskan apa yang saya coba lakukan:

# 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
sumber
5
mendarat di sini mencari cara untuk memiliki kondisi dalam pernyataan pembuat set, pertanyaan Anda menjawab pertanyaan saya :)
Anuvrat Parashar
5
split adalah deskripsi yang tidak menguntungkan dari operasi ini, karena sudah memiliki arti khusus sehubungan dengan string Python. Saya pikir membagi adalah kata yang lebih tepat (atau setidaknya kelebihan beban dalam konteks iterables Python) untuk menggambarkan operasi ini. Saya mendarat di sini mencari daftar yang setara dengan str.split(), untuk membagi daftar menjadi koleksi sub-daftar berurutan. Misalnya split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6]), sebagai lawan untuk membagi elemen daftar berdasarkan kategori.
Stew
Diskusi topik yang sama pada daftar python.
Xiong Chiamiov
IMAGE_TYPES harus satu set bukannya tupel: IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (1) bukannya n (o / 2), dengan praktis tidak ada perbedaan dalam keterbacaan.
ChaimG

Jawaban:

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

apakah ada cara yang lebih elegan untuk melakukan ini?

Kode itu mudah dibaca, dan sangat jelas!

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

Sekali lagi, ini baik - baik saja!

Mungkin ada sedikit peningkatan kinerja menggunakan set, tetapi ini adalah perbedaan yang sepele, dan saya menemukan pemahaman daftar jauh lebih mudah untuk dibaca, dan Anda tidak perlu khawatir tentang urutan yang kacau, duplikat dihapus seperti itu.

Bahkan, saya dapat melangkah ke belakang "mundur", dan cukup gunakan sederhana untuk loop:

images, anims = [], []

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

Daftar-pemahaman atau penggunaan set()baik-baik saja sampai Anda perlu menambahkan beberapa cek lain atau sedikit logika - katakan Anda ingin menghapus semua jpeg 0-byte, Anda hanya menambahkan sesuatu seperti ..

if f[1] == 0:
    continue
dbr
sumber
44
Apakah tidak ada cara pemahaman daftar tanpa harus mengulang daftar dua kali?
balki
35
Masalahnya adalah ini melanggar prinsip KERING. Akan lebih baik jika ada cara yang lebih baik untuk melakukan ini.
Antimony
21
Setelah selera untuk pemrograman fungsional (Haskell), atau gaya fungsional (LINQ) dinaikkan, kita mulai mencium bau Python untuk usianya - [x for x in blah if ...]- verbose, lambdakikuk dan terbatas ... Rasanya seperti mengendarai mobil paling keren dari 1995 hari ini. Tidak sama dengan saat itu.
Tomasz Gandor
6
@TomaszGandor FTR, Haskell lebih tua dari Python (dan sebenarnya mempengaruhi desainnya). Saya pikir sintaksis untuk pemahaman daftar dan lambdas sengaja dibuat sedikit di sisi verbose, mungkin untuk mencegah penggunaan yang berlebihan. Yang memang sedikit risiko ... sebanyak saya suka Haskell, saya bisa melihat mengapa banyak orang menemukan Python umumnya lebih mudah dibaca.
leftaroundabout
4
sederhana untuk loop adalah cara terbaik untuk melakukan ini ... satu loop, sangat jelas dan mudah dibaca
Anentropic
217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)
John La Rooy
sumber
14
Itu sangat cerdik! Butuh beberapa saat untuk mengerti apa yang terjadi. Saya ingin tahu apakah orang lain berpikir ini dapat dianggap sebagai kode yang dapat dibaca atau tidak.
jgpaiva
171
good.append(x) if x in goodvals else bad.append(x)lebih mudah dibaca.
dansalmo
21
@dansalmo Terutama karena Anda dapat menjadikannya one-liner dengan for-cycle, dan jika Anda ingin menambahkan sesuatu yang lebih rumit daripada x, Anda dapat menjadikannya appendhanya satu :for x in mylist: (good if isgood(x) else bad).append(x)
yo '
2
@ Master, dalam hal ini Anda mungkin harus memasukkan atribut lookup(bad.append, good.append)
John La Rooy
11
Variasi yang sedikit lebih pendek:(good if x in goodvals else bad).append(x)
Pi Delport
104

Inilah pendekatan iterator yang malas:

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)

Ini mengevaluasi kondisi sekali per item dan mengembalikan dua generator, pertama menghasilkan nilai dari urutan di mana kondisi itu benar, yang lain di mana itu salah.

Karena malas, Anda dapat menggunakannya di sembarang iterator, bahkan yang tak terbatas:

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

Biasanya meskipun pendekatan pengembalian daftar yang tidak malas lebih baik:

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

Sunting: Untuk usecase Anda yang lebih spesifik dari membagi item ke dalam daftar yang berbeda dengan beberapa kunci, inilah fungsi generik yang melakukan itu:

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

Pemakaian:

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']
Semut Aasma
sumber
Anda mungkin benar bahwa ini melanggar prinsip YAGNI. Hal ini didasarkan pada asumsi bahwa sejumlah daftar yang berbeda bahwa hal-hal yang dapat dipartisi akan tumbuh di masa depan.
Ants Aasma
17
Mungkin banyak kode tetapi jika [ x for x in my_list if ExpensiveOperation(x) ]perlu waktu lama untuk dijalankan, Anda tentu tidak ingin melakukannya dua kali!
dash-tom-bang
1
+1 untuk menawarkan beberapa variasi termasuk berbasis iterator dan solusi "in X" tertentu. "Dalam keadaan baik" OP mungkin kecil, tetapi mengganti ini dengan kamus yang sangat besar atau predikat mahal bisa mahal. Juga mengurangi kebutuhan untuk menulis daftar pemahaman dua kali di mana saja diperlukan, sehingga mengurangi kemungkinan untuk memperkenalkan kesalahan ketik / pengguna. Solusi bagus Terima kasih!
cod3monk3y
3
Perhatikan bahwa teemenyimpan semua nilai di antara iterator yang dikembalikan, sehingga tidak akan benar-benar menghemat memori jika Anda mengulangi seluruh generator dan yang lainnya.
John La Rooy
25

Masalah dengan semua solusi yang diajukan adalah akan memindai dan menerapkan fungsi penyaringan dua kali. Saya akan membuat fungsi kecil sederhana seperti ini:

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

Dengan begitu Anda tidak memproses apa pun dua kali dan juga tidak mengulang kode.

Winden
sumber
Saya setuju. Saya sedang mencari cara "elegan" (yaitu di sini yang berarti pendek dan built-in / implisit) untuk melakukan ini tanpa memindai daftar dua kali, tetapi ini tampaknya (tanpa profil) menjadi cara untuk pergi. Tentu saja itu hanya masalah bagi data dalam jumlah besar.
Matthew Flaschen
IMHO, jika Anda tahu cara melakukannya dengan penggunaan cpu yang lebih sedikit (dan dengan demikian mengurangi daya), tidak ada alasan untuk tidak menggunakannya.
winden
2
@winden ... Mengaitkan semua Python saya ke C.;)
Elliot Cameron
19

Saya ambil itu. Saya mengusulkan fungsi lazy, single-pass partition, yang menjaga urutan relatif pada output berikutnya.

1. Persyaratan

Saya berasumsi bahwa persyaratannya adalah:

  • menjaga urutan relatif elemen (karenanya, tidak ada set dan kamus)
  • mengevaluasi kondisi hanya sekali untuk setiap elemen (karenanya tidak menggunakan ( i) filteratau groupby)
  • memungkinkan untuk konsumsi malas dari salah satu urutan (jika kita mampu melakukan precompute mereka, maka implementasi naif kemungkinan juga dapat diterima)

2. splitperpustakaan

partitionFungsi saya (diperkenalkan di bawah) dan fungsi serupa lainnya telah membuatnya menjadi perpustakaan kecil:

Ini dapat diinstal secara normal melalui PyPI:

pip install --user split

Untuk membagi basis daftar pada kondisi, gunakan partitionfungsi:

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

Secara internal kita perlu membangun dua urutan sekaligus, jadi hanya memakan satu urutan output akan memaksa yang lain untuk dihitung juga. Dan kita perlu menjaga keadaan antara permintaan pengguna (toko diproses tetapi elemen belum diminta). Untuk menjaga status, saya menggunakan dua antrian ujung ganda ( deques):

from collections import deque

SplitSeq kelas mengurus rumah tangga:

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

Sihir terjadi dalam .getNext()metodenya. Ini hampir seperti .next() iterator, tetapi memungkinkan untuk menentukan jenis elemen yang kita inginkan kali ini. Di belakang adegan itu tidak membuang elemen yang ditolak, tetapi sebaliknya menempatkan mereka di salah satu dari dua antrian:

    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)

Pengguna akhir seharusnya menggunakan partitionfungsi. Dibutuhkan fungsi kondisi dan urutan (seperti mapatau filter), dan mengembalikan dua generator. Generator pertama membangun urutan elemen yang kondisinya berlaku, generator kedua membangun urutan tambahan. Iterator dan generator memungkinkan pemisahan dengan urutan yang panjang atau tidak terbatas.

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

Saya memilih fungsi tes untuk menjadi argumen pertama untuk memfasilitasi aplikasi parsial di masa depan (mirip dengan bagaimana mapdan filter memiliki fungsi tes sebagai argumen pertama).

sastanin
sumber
15

Saya pada dasarnya menyukai pendekatan Anders karena sangat umum. Berikut adalah versi yang mengutamakan kategorizer (untuk mencocokkan sintaks filter) dan menggunakan defaultdict (diasumsikan diimpor).

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
sumber
Saya akan mencoba untuk mengambil pernyataan dari Zen Python yang berlaku di sini, tetapi terlalu banyak untuk komentar. =) Sepotong kode yang luar biasa.
jpmc26
13

Go pertama (pra-OP-edit): Gunakan 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]

Itu bagus untuk keterbacaan (IMHO) dan kinerja.

Go kedua (post-OP-edit):

Buat daftar ekstensi yang baik sebagai satu set:

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

dan itu akan meningkatkan kinerja. Kalau tidak, apa yang Anda miliki terlihat baik bagi saya.

RichieHindle
sumber
4
bukan solusi terbaik jika daftar berada dalam urutan sebelum dipisah dan Anda membutuhkannya untuk tetap dalam urutan itu.
Daniyar
8
Bukankah itu menghapus duplikat?
mavnn
Membuat set adalah O (n log n). Iterasi daftar dua kali adalah O (n). Solusi himpunan mungkin lebih elegan (ketika itu benar di tempat pertama) tetapi pasti lebih lambat karena n meningkat.
dash-tom-bang
1
@ dash-tom-bang Iterasi daftar adalah O (n * n). Itu karena setiap item dalam daftar mungkin perlu dibandingkan dengan setiap item dalam goodvals.
ChaimG
@ ChaimG poin bagus, meskipun kita juga perlu mempertimbangkan biaya operasi persimpangan dan perbedaan (yang saya tidak tahu tapi saya cukup yakin mereka superlinear juga).
dash-tom-bang
10

itertools.groupby hampir melakukan apa yang Anda inginkan, kecuali itu membutuhkan item yang akan disortir untuk memastikan bahwa Anda mendapatkan satu rentang yang berdekatan, jadi Anda perlu mengurutkan berdasarkan kunci Anda terlebih dahulu (jika tidak, Anda akan mendapatkan beberapa grup yang saling terkait untuk setiap jenis). misalnya.

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)

memberi:

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

Mirip dengan solusi lain, fungsi kunci dapat didefinisikan untuk dibagi menjadi sejumlah grup yang Anda inginkan.

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

Jawaban elegan dan ringkas oleh @dansalmo ini muncul di komentar, jadi saya hanya mengepos ulang di sini sebagai jawaban sehingga bisa mendapatkan keunggulan yang layak, terutama bagi pembaca baru.

Contoh lengkap:

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

Jika Anda ingin membuatnya dalam gaya FP:

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

Bukan solusi yang paling mudah dibaca, tetapi paling tidak hanya melalui mylist sekali saja.

michau
sumber
1
Meskipun hanya sekali dalam satu daftar, kinerjanya tidak begitu bagus karena daftar tersebut ditambahkan. Menambahkan ke daftar berpotensi operasi mahal (bila dibandingkan dengan deque.append misalnya). Sebenarnya, solusi ini sangat lambat jika dibandingkan dengan solusi lain di sini (21,4 detik pada 100000 bilangan bulat acak dan menguji nilainya).
rlat
5

Secara pribadi, saya suka versi yang Anda kutip, dengan asumsi Anda sudah memiliki daftar goodvalsnongkrong. Jika tidak, sesuatu seperti:

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

Tentu saja, itu sangat mirip dengan menggunakan pemahaman daftar seperti yang Anda lakukan semula, tetapi dengan fungsi alih-alih pencarian:

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

Secara umum, saya menemukan estetika pemahaman daftar menjadi sangat menyenangkan. Tentu saja, jika Anda tidak benar-benar perlu mempertahankan pemesanan dan tidak perlu duplikat, menggunakan intersectiondan differencemetode pada set akan bekerja dengan baik juga.

BJ Homer
sumber
Tentu saja, filter(lambda x: is_good(x), mylist)dapat direduksi menjadifilter(is_good, mylist)
robru
menambahkan pemanggilan fungsi tambahan sebenarnya menggandakan (!) waktu eksekusi, dibandingkan dengan pemahaman daftar, dari apa yang saya lihat dalam pembuatan profil. sulit untuk mengalahkan pemahaman daftar, sebagian besar waktu.
Corley Brigman
4

Saya pikir generalisasi dari pemisahan iterable berdasarkan kondisi N berguna

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

Misalnya:

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]

Jika elemen dapat memenuhi beberapa kondisi, hapus jeda.

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

Lihat ini

Hanfei Sun
sumber
3

Terkadang, sepertinya pemahaman daftar bukan hal terbaik untuk digunakan!

Saya membuat tes kecil berdasarkan jawaban yang diberikan orang pada topik ini, diuji pada daftar yang dibuat secara acak. Inilah generasi daftar (mungkin ada cara yang lebih baik untuk dilakukan, tetapi bukan itu intinya):

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

Dan di sini kita mulai

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

Menggunakan fungsi cmpthese , hasil terbaik adalah jawaban 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
sumber
Fungsi lebih cepat dengan tolok ukur yang diperbarui di sini .
ChaimG
2

Namun solusi lain untuk masalah ini. Saya membutuhkan solusi yang secepat mungkin. Itu berarti hanya satu iterasi di atas daftar dan lebih disukai O (1) untuk menambahkan data ke salah satu daftar yang dihasilkan. Ini sangat mirip dengan solusi yang disediakan oleh sastanin , kecuali jauh lebih pendek:

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

Kemudian, Anda dapat menggunakan fungsi dengan cara berikut:

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

Jika Anda tidak baik dengan yang dihasilkan dequeobjek, Anda dapat dengan mudah dikonversi ke list, set, apa pun yang Anda seperti (misalnya list(lower)). Konversi jauh lebih cepat, yaitu pembangunan daftar secara langsung.

Metode ini menjaga urutan barang, serta duplikat.

rlat
sumber
2

Misalnya, daftar pemisahan dengan genap dan ganjil

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

Atau secara umum:

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

Keuntungan:

  • Cara kemungkinan terpendek
  • Predikat hanya berlaku satu kali untuk setiap elemen

Kekurangan

  • Membutuhkan pengetahuan tentang paradigma pemrograman fungsional
Pavel Ilchenko
sumber
2

Terinspirasi oleh jawaban hebat @ gnibbler (tetapi singkat!) , Kita dapat menerapkan pendekatan itu untuk memetakan ke beberapa partisi:

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

Kemudian splitterbisa digunakan sebagai berikut:

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

Ini berfungsi untuk lebih dari dua partisi dengan pemetaan yang lebih rumit (dan pada iterator juga):

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

Atau menggunakan kamus untuk memetakan:

>>> 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
sumber
... hanya memperhatikan ini pada dasarnya sama dengan @ alan-isaac sudah menjawab.
Josh Bode
2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

tambahkan pengembalian Tidak ada, jadi itu berfungsi.

Biga
sumber
1

Untuk kinerja, cobalah itertools.

The itertools modul standarisasi inti set cepat, memori alat yang efisien yang berguna sendiri atau dalam kombinasi. Bersama-sama, mereka membentuk "aljabar iterator" sehingga memungkinkan untuk membuat alat khusus secara ringkas dan efisien dalam Python murni.

Lihat itertools.ifilter atau imap.

itertools.ifilter (predikat, iterable)

Buat iterator yang memfilter elemen dari iterable hanya mengembalikan yang predikatnya Benar

Gimel
sumber
ifilter / imap (dan generator pada umumnya) sangat lambat ... secara umum, dalam profiling saya, jika Anda mengambil daftar pemahaman seperti [x for x in a if x > 50000]pada array sederhana 100000 bilangan bulat (melalui random.shuffle), filter(lambda x: x> 50000, a)akan memakan waktu 2x lebih lama, ifilter(lambda x: x> 50000, a); list(result)membutuhkan waktu sekitar 2.3x selama. Aneh tapi Nyata.
Corley Brigman
1

Terkadang Anda tidak membutuhkan separuh daftar lainnya. Sebagai contoh:

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 '')
Mal Shikhar
sumber
1

Ini cara tercepat.

Ini menggunakan if else, (seperti jawaban dbr) tetapi membuat satu set pertama. Satu set mengurangi jumlah operasi dari O (m * n) ke O (log m) + O (n), menghasilkan 45% + peningkatan kecepatan.

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)

Sedikit lebih pendek:

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)

Hasil patok banding:

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

Kode benchmark lengkap untuk Python 3.7 (dimodifikasi dari 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
sumber
0

Jika Anda bersikeras pandai, Anda bisa mengambil solusi Winden dan sedikit kepintaran palsu:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d
Anders Eurenius
sumber
3
"D or {}" agak berbahaya. Jika dikte kosong dilewatkan, maka tidak akan dimutasi di tempat.
Brian
Benar, tetapi dikembalikan, jadi ... Sebenarnya, ini adalah contoh sempurna mengapa Anda tidak ingin menambahkan lebih pintar ke kode Anda. :-P
Anders Eurenius
0

Sudah beberapa solusi di sini, tetapi cara lain untuk melakukan itu adalah -

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

Iterasi daftar hanya sekali, dan terlihat sedikit lebih pythonic dan karenanya dapat dibaca oleh saya.

>>> 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
sumber
0

Saya akan mengambil pendekatan 2-pass, memisahkan evaluasi predikat dari memfilter daftar:

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

Apa yang baik tentang ini, kinerja-bijaksana (selain mengevaluasi predhanya sekali pada setiap anggota iterable), adalah bahwa ia banyak memindahkan logika dari penerjemah dan ke dalam iterasi yang sangat dioptimalkan dan kode pemetaan. Ini dapat mempercepat iterasi dari iterables yang panjang, seperti dijelaskan dalam jawaban ini .

Dari sisi ekspresif, ia memanfaatkan idiom ekspresif seperti pemahaman dan pemetaan.

Jim Witschey
sumber
0

larutan

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

uji

[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
sumber
0

Jika Anda tidak keberatan menggunakan perpustakaan eksternal di sana dua saya tahu bahwa secara nativ mengimplementasikan operasi ini:

>>> 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
sumber
0

Tidak yakin apakah ini pendekatan yang baik tetapi bisa dilakukan dengan cara ini juga

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
sumber
0

Jika daftar terbuat dari grup dan pemisah berselang, Anda dapat menggunakan:

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

Pemakaian:

split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
David
sumber
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]

Bagus ketika kondisinya lebih lama, seperti pada contoh Anda. Pembaca tidak harus mencari tahu kondisi negatif dan apakah ia menangkap semua kasus lainnya.

Chrisjan
sumber
0

Namun jawaban lain, pendek tapi "jahat" (untuk efek samping daftar-pemahaman).

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
sumber