Python: bir koşula göre bir liste bölmek?


272

Bir madde listesini bir koşullu duruma göre birden çok listeye bölmenin hem estetik hem de performans açısından en iyi yolu nedir? Eşdeğeri:

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

Bunu yapmanın daha zarif bir yolu var mı?

Güncelleme: Yapmaya çalıştığım şeyi daha iyi açıklamak için asıl kullanım örneği:

# 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
set oluşturucu ifadesinde bir koşula sahip olmak için bir yol arayan buraya indi, sorunuz sorumu yanıtladı :)
Anuvrat Parashar

5
split , bu işlemin talihsiz bir açıklamasıdır, çünkü zaten Python dizeleri ile ilgili özel bir anlamı vardır. Bence bölmek bu işlemi açıklamak için daha kesin bir (veya daha az Python Iterables bağlamında aşırı en az) bir kelimedir. Ben bir listesi eşdeğer arayan buraya indi str.split()etmek, bölmek ardışık alt listelerinin sıralı koleksiyonuna listesi. Örneğin split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6]), olarak karşı bölünmesi kategoriye göre bir listenin elemanları.
Güveç

Aynı konunun python listesinde tartışılması .
Xiong Chiamiov

IMAGE_TYPES bir demet: yerine bir küme olmalıdır IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (o / 2) yerine n (1), okunabilirlikte hiçbir fark yoktur.
ChaimG

Yanıtlar:


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

Bunu yapmanın daha zarif bir yolu var mı?

Bu kod mükemmel bir şekilde okunabilir ve son derece açıktır!

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

Yine, bu iyi!

Kümeleri kullanarak küçük performans iyileştirmeleri olabilir, ancak bu önemsiz bir farktır ve liste kavrayışının okunmasını çok daha kolay buluyorum ve siparişin dağılması konusunda endişelenmenize gerek yok, kopyalar bu şekilde kaldırılıyor.

Aslında, başka bir adım "geri" gidebilir ve sadece döngü için basit bir kullanabilirsiniz:

images, anims = [], []

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

Listeyi anlama veya kullanma set(), başka bir kontrol veya başka bir mantık biti eklemeniz gerekene kadar iyidir - tüm 0 bayt jpeg'leri kaldırmak istediğinizi söyleyin, sadece bir şey ekleyin ..

if f[1] == 0:
    continue

44
Listede iki kez dolaşmak zorunda kalmadan bir liste anlama yolu yok mu?
balki

35
Sorun, bunun KURU ilkesini ihlal etmesidir. Bunu yapmanın daha iyi bir yolu olsaydı iyi olurdu.
Antimon

21
Fonksiyonel programlama (Haskell) veya fonksiyonel stil (LINQ) için iştah arttığında, Python'un yaşı için kokmaya başlıyoruz [x for x in blah if ...]- ayrıntılı, lambdabeceriksiz ve sınırlıdır ... 1995'ten günümüze en havalı arabayı sürmek gibi geliyor. O zamanki gibi değil.
Tomasz Gandor

6
@TomaszGandor FTR, Haskell Python'dan daha yaşlı (ve aslında tasarımını etkiledi). Bence liste kavraması ve lambdaların sözdizimi, belki de onları aşırı kullanmaktan caydırmak için kasıtlı olarak biraz ayrıntılı tarafta tutuldu. Bu gerçekten biraz risk ... Haskell'i sevdiğim kadarıyla, birçok insanın neden Python'u genellikle daha okunabilir bulduğunu görebiliyorum.
leftaroundabout

4
döngü için basit bunu yapmak için en iyi yoldur ... tek bir döngü, çok açık ve okunabilir
Anentropic

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

14
Bu inanılmaz derecede ustaca! Gerçi ne olduğunu anlamak biraz zamanımı aldı. Başkalarının bunun okunabilir kod olarak kabul edilip edilemeyeceğini düşünüp düşünmediğini bilmek istiyorum.
jgpaiva

171
good.append(x) if x in goodvals else bad.append(x)daha okunabilir.
dansalmo

21
@dansalmo Özellikle de for-döngüsü ile bir astar yapabileceğiniz için ve daha karmaşık bir şey eklemek xistiyorsanız, bunu appendsadece bir tanesine yapabilirsiniz :for x in mylist: (good if isgood(x) else bad).append(x)
yo

2
@MLister, bu durumda muhtemelen özellik aramasını dahil etmelisiniz(bad.append, good.append)
John La Rooy

11
Biraz daha kısa bir değişiklik:(good if x in goodvals else bad).append(x)
Pi Delport

104

İşte tembel yineleyici yaklaşımı:

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)

Her öğe için koşulu bir kez değerlendirir ve ilk önce koşulun doğru olduğu diziden, diğerinin yanlış olduğu durumlarda değerler vererek iki jeneratör döndürür.

Tembel olduğu için, herhangi bir yineleyicide, hatta sonsuzda bile kullanabilirsiniz:

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

Genellikle tembel olmayan liste geri dönen yaklaşım daha iyi olsa da:

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

Düzenleme: Bazı tuşlarla öğeleri farklı listelere bölme daha özel bir kullanım için, bunu yapan genel bir işlev vardır:

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

Kullanımı:

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

Muhtemelen bunun YAGNI prensibini ihlal ettiği konusunda haklısınız. İleride bölümlere ayrılabilecek farklı listelerin sayısının gelecekte artacağı varsayımına dayanmaktadır.
Karıncalar Aasma

17
Çok fazla kod olabilir, ancak [ x for x in my_list if ExpensiveOperation(x) ]çalıştırılması uzun zaman alırsa , kesinlikle iki kez yapmak istemezsiniz!
dash-tom-bang

1
Yineleyici tabanlı ve belirli bir "X'de" çözüm dahil olmak üzere birden çok varyasyon sunmak için +1. OP'nin "iyi değerlerde" küçük olabilir, ancak bunu çok büyük bir sözlük veya pahalı yüklemle değiştirmek pahalı olabilir. Ayrıca, listenin anlaşılmasını ihtiyaç duyulan her yerde iki kez yazma ihtiyacını azaltır , böylece yazım hatası / kullanıcı hatası girme olasılığını azaltır. Güzel çözüm. Teşekkürler!
cod3monk3y

3
Not olduğunu teedöndürür yineleyiciler arasındaki depolar tüm değerler, bu olacak kadar değil gerçekten belleğe kaydetme eğer döngü üzerinden bir bütün jeneratör ve sonra diğeri.
John La Rooy

25

Önerilen tüm çözümlerle ilgili sorun, filtreleme işlevini iki kez taraması ve uygulamasıdır. Ben böyle basit bir küçük işlev yapmak istiyorum:

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

Bu şekilde hiçbir şeyi iki kez işlemezsiniz ve aynı zamanda kodu tekrarlamazsınız.


Katılıyorum. Ben listeyi iki kez taramadan bunu yapmak için "zarif" (yani burada kısa ve yerleşik / örtük anlamında) bir yol arıyordum, ama bu (profil olmadan) gitmek için bir yol gibi görünüyor. Tabii ki sadece büyük miktarda veri için her durumda önemlidir.
Matthew Flaschen

IMHO, daha az işlemci kullanımı (ve dolayısıyla daha az güç tüketimi) ile yapmanın bir yolunu biliyorsanız, kullanmamanın bir nedeni yoktur.
winden

2
@winden ... Tüm Python'umu C.'ye taşıyor;)
Elliot Cameron

19

Benim almam. partitionÇıktı dizilerinde göreli düzeni koruyan tembel, tek geçişli bir işlev öneriyorum .

1. Gereksinimler

Gereksinimlerin olduğunu varsayıyorum:

  • öğelerin göreli sırasını koru (bu nedenle, kümeler ve sözlükler yok)
  • koşulu her öğe için yalnızca bir kez değerlendirin (dolayısıyla ( i) filterveya kullanmayın groupby)
  • her iki sekansın tembel tüketimine izin verin (eğer bunları önceden hesaplayabilirsek, naif uygulama da kabul edilebilir olacaktır)

2. splitkütüphane

İşlevim partition(aşağıda tanıtıldı) ve diğer benzer işlevler, küçük bir kütüphaneye dönüştürdü:

Normalde PyPI ile kurulabilir:

pip install --user split

Bir liste tabanını koşullara ayırmak için partitionişlevi kullanın :

>>> 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. partitionfonksiyon açıklaması

Dahili olarak bir kerede iki alt sekans oluşturmamız gerekir, bu nedenle sadece bir çıkış sekansı tüketmek diğerini de hesaplamaya zorlar. Ayrıca, kullanıcı istekleri (mağaza işlenmiş ancak henüz talep edilmemiş öğeler) arasında durumu korumamız gerekir. Durumu korumak için iki çift uçlu kuyruk ( deques) kullanıyorum:

from collections import deque

SplitSeq sınıf oda temizliği ile ilgilenir:

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

Büyü .getNext()yönteminde olur . Neredeyse .next() yineleyiciler gibidir, ancak bu sefer ne tür bir öğe istediğimizi belirtmeye izin verir. Sahnenin arkasında, reddedilen öğeleri atmaz, bunun yerine bunları iki kuyruktan birine yerleştirir:

    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)

Son kullanıcının partitionişlevi kullanması gerekir. Bir koşul işlevi ve bir dizi (tıpkı mapveya gibi filter) alır ve iki jeneratör döndürür. İlk jeneratör, koşulun bulunduğu öğelerin bir alt dizisini oluşturur, ikincisi tamamlayıcı alt diziyi oluşturur. Yineleyiciler ve jeneratörler, uzun veya sonsuz dizilerin bile tembel olarak bölünmesine izin verir.

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

Gelecekte kısmi uygulamayı kolaylaştıran ilk argüman olarak test fonksiyonunu seçtim (test fonksiyonunun ilk argüman olarak nasıl mapve nasıl filter olduğu gibi).


15

Anders'in yaklaşımını çok genel olduğu için seviyorum. Burada, kategorizatörü ilk sıralayan (filtre sözdizimiyle eşleşecek) ve bir defaultdict (içe aktarıldığı varsayılır) kullanan bir sürüm.

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

Burada geçerli olan Python Zen ifadelerini seçmeye çalışacaktım , ancak yorum yapmak için çok fazla. =) Müthiş kod parçası.
jpmc26

13

İlk git (OP-düzenleme öncesi): Kümeleri kullan:

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]

Bu hem okunabilirlik (IMHO) hem de performans için iyidir.

İkinci hareket (OP-düzenleme sonrası):

İyi uzantılar listenizi küme olarak oluşturun:

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

ve bu performansı artıracaktır. Aksi halde, sahip olduğun şey bana iyi geliyor.


4
listeler bölmeden önce bir sırada olsaydı ve bu sırada kalmak için onlara ihtiyacınız varsa en iyi çözüm değil.
Daniyar

8
Bu kopyaları kaldırmaz mı?
mavnn

Bir küme oluşturmak O (n log n) şeklindedir. Listeyi iki kez yinelemek O (n) 'dir. Ayarlanan çözüm daha şık olabilir (ilk başta doğru olduğunda), ancak n arttıkça kesinlikle daha yavaştır.
dash-tom-bang

1
@ dash-tom-bang Listeyi yinelemek O (n * n) şeklindedir. Bunun nedeni, listedeki her öğenin içindeki her öğeyle karşılaştırılması gerekebilir goodvals.
ChaimG

@ChaimG iyi bir nokta olsa da, kavşak ve fark operasyonlarının maliyetini de dikkate almamız gerekiyor (ki elden bilmiyorum ama onların da süper doğrusal olduklarından eminim).
dash-tom-bang

10

itertools.groupby neredeyse istediğinizi yapar, ancak tek bir bitişik aralık elde ettiğinizden emin olmak için öğelerin sıralanmasını gerektirir, bu nedenle önce anahtarınıza göre sıralamanız gerekir (aksi takdirde her tür için birden fazla araya eklenmiş grup alırsınız). Örneğin.

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)

verir:

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

Diğer çözümlere benzer şekilde, anahtar işlevi istediğiniz sayıda gruba bölünecek şekilde tanımlanabilir.


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

@Dansalmo'nun bu zarif ve özlü cevabı yorumlarda gömüldü, bu yüzden burada bir cevap olarak yeniden gönderiyorum, özellikle de yeni okuyucular için hak ettiği önemi alabiliyor.

Komple örnek:

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

5

FP tarzında yapmak istiyorsanız:

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

En okunabilir çözüm değil, ancak en azından bir kez mylist üzerinden tekrarlar.


1
Liste boyunca yalnızca bir kez yinelenmesine rağmen, listenin eklenmesi nedeniyle performans o kadar iyi değildir. Bir listeye eklemek potansiyel olarak pahalı bir işlemdir (örneğin deque.append ile karşılaştırıldığında). Aslında, bu çözüm diğer çözümlerle karşılaştırıldığında son derece yavaştır (100000 rasgele tamsayıda 21.4s ve değerlerini test etmek).
rlat

5

Şahsen, goodvalstakıldığınız versiyonu sevdim, takılmak için bir listeniz zaten var . Değilse, şöyle bir şey:

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

Tabii ki, bu aslında başlangıçta yaptığınız gibi bir liste kavrayışı kullanmaya çok benziyor, ancak arama yerine bir işlevle:

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

Genel olarak, liste anlayışlarının estetiğini çok hoş buluyorum. Tabii ki, aslında siparişi korumanıza ve kopyalara ihtiyacınız yoksa , setlerdeki intersectionve differenceyöntemlerini kullanmak da iyi çalışır.


Tabii ki, filter(lambda x: is_good(x), mylist)azaltılabilirfilter(is_good, mylist)
robru

Ekstra fonksiyon çağrısının eklenmesi, profillemede gördüğümden, liste kavrayışlarına kıyasla yürütme süresini iki katına çıkarır (!). çoğu zaman bir liste kavrayışını yenmek zor.
Corley Brigman

4

Bence N koşullarına dayalı bir yinelenebilir bölme genellemesi kullanışlı

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

Örneğin:

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]

Eleman birden fazla koşulu karşılayabiliyorsa, molayı kaldırın.


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)

Bunu kontrol et


3

Bazen, liste kavraması kullanmak için en iyi şey değil gibi görünüyor!

İnsanların bu konuya verdikleri cevaba göre küçük bir test yaptım, rastgele oluşturulmuş bir listede test ettim. İşte listenin oluşturulması (muhtemelen daha iyi bir yol var, ama mesele bu değil):

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

Ve işte başlıyoruz

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

Cmpthese işlevini kullanarak , en iyi sonuç dbr cevabıdır:

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

Burada güncellenen karşılaştırmalı değerlendirmelerle daha hızlı işlevler .
ChaimG

2

Yine bu soruna başka bir çözüm. Mümkün olduğunca hızlı bir çözüme ihtiyacım vardı. Bu, liste üzerinde sadece bir yineleme ve tercihen sonuç listelerinden birine veri eklemek için O (1) anlamına gelir. Bu, çok daha kısa hariç, sastanin tarafından sağlanan çözeltiye çok benzer :

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

Ardından, işlevi aşağıdaki şekilde kullanabilirsiniz:

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

Eğer ortaya çıkan değil sorun yoktur dequenesne, kolayca dönüştürebilirsiniz list, setne olursa olsun sizin gibi (örneğin list(lower)). Dönüşüm çok daha hızlı, doğrudan listelerin oluşturulması.

Bu yöntemler, öğelerin ve kopyaların sırasını korur.


2

Örneğin, listeyi çift ve tek olarak bölme

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

Veya genel olarak:

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

Avantajları:

  • En kısa yol
  • Tahmin her öğe için yalnızca bir kez uygulanır

Dezavantajları

  • İşlevsel programlama paradigması hakkında bilgi gerektirir

2

@ Gnibbler'ın harika (ama kısa!) Cevabından esinlenerek , bu yaklaşımı birden fazla bölüme eşlemek için uygulayabiliriz:

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

Daha splittersonra aşağıdaki gibi kullanılabilir:

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

Bu, daha karmaşık bir haritalamaya sahip ikiden fazla bölüm için işe yarar (ve yineleyiciler üzerinde de):

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

Veya eşlemek için bir sözlük kullanarak:

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

... bunun temelde @ alan-isaac'ın cevapladığı gibi olduğunu fark ettim.
Josh Bode

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

append hiçbiri döndürür, bu yüzden çalışır.


1

Performans için deneyin itertools.

İtertools modül hızlı bir çekirdek dizi, kendi başlarına ya da kombinasyon halinde yararlı olan bellek etkin araçlar standart hale getirmektedir. Birlikte, saf Python'da özlü ve verimli bir şekilde özel araçlar inşa etmeyi mümkün kılan bir “yineleyici cebiri” oluştururlar.

Bkz. İtertools.ifilter veya imap.

itertools.ifilter (yüklem, yinelenebilir)

Yalnızca yüklemin doğru olduğu öğeleri döndürerek öğeleri yinelemeden filtreleyen bir yineleyici yapın


ifilter / imap (ve genel olarak jeneratörler) oldukça yavaştır ... genel olarak, profilimde [x for x in a if x > 50000], 100000 tamsayıdan oluşan basit bir dizi (random.shuffle aracılığıyla) gibi bir liste kavrama alırsanız, filter(lambda x: x> 50000, a)2x uzun sürecek, ifilter(lambda x: x> 50000, a); list(result)alır yaklaşık 2.3x uzunluğunda. Tuhaf ama gerçek.
Corley Brigman

1

Bazen listenin diğer yarısına ihtiyacınız olmaz. Örneğin:

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

Bu en hızlı yol.

if else(Dbr'nin cevabı gibi) kullanır , ancak önce bir küme oluşturur. Bir set, O (m * n) 'den O (log m) + O (n)' ye olan işlem sayısını azaltır, bu da hızda% 45 + artış sağlar.

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)

Biraz daha kısa:

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)

Deney sonuçları:

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

Python 3.7 için tam karşılaştırma kodu (FunkySayu'dan değiştirildi):

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

Akıllıca ısrar ediyorsanız, Winden'in çözümünü ve sadece biraz akıllı zekayı alabilirsiniz:

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

3
"D veya {}" biraz tehlikelidir. Boş bir dikte geçilirse, mutasyona uğramaz.
Brian

Doğru, ama döndü alır, yani ... Aslında bu neden mükemmel bir örnektir yok Kodunuza daha zeki eklemek istiyorum. :-P
Anders Eurenius

0

Zaten burada birkaç çözüm var, ama bunu yapmanın başka bir yolu -

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

Listeyi yalnızca bir kez yineliyor ve biraz daha pitonik görünüyor ve bu yüzden bana okunabilir görünüyor.

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

Yüklemenin değerlendirmesini listeyi filtrelemekten ayıran 2 geçişli bir yaklaşım benimseyeceğim:

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

Bu konuda güzel olan, performans açısından ( predher bir üyesinde sadece bir kez değerlendirmenin yanı sıra iterable), çok fazla mantığı yorumlayıcıdan ve son derece optimize edilmiş yineleme ve eşleme koduna taşımasıdır. Bu, bu cevapta açıklandığı gibi uzun tekrarlanabilirler üzerinde yinelemeyi hızlandırabilir .

Anlatımcılık açısından, anlama ve haritalama gibi etkileyici deyimlerden yararlanır.


0

çözüm

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

Ölçek

[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

Harici bir kütüphane kullanmak sakıncası yoksa orada iki biliyorum bu işlemi doğal olarak uygulamak:

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

Bunun iyi bir yaklaşım olup olmadığından emin değilim ama bu şekilde de yapılabilir

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

Liste gruplardan ve aralıklı ayırıcılardan oluşuyorsa, şunları kullanabilirsiniz:

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

Kullanımı:

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]

Durum daha uzun olduğunda güzel, örneğin. Okuyucunun olumsuz durumu ve diğer tüm durumları yakalayıp yakalamadığını anlaması gerekmez.


0

Yine başka bir cevap, kısa ama "kötü" (liste-kavrama yan etkileri için).

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]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.