Python: розділити список на основі умови?


272

Який найкращий спосіб, як естетично, так і з точки зору продуктивності, розділити список елементів на кілька списків на основі умовного? Еквівалент:

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

чи є більш елегантний спосіб це зробити?

Оновлення: ось фактичний випадок використання, щоб краще пояснити, що я намагаюся зробити:

# 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
приземлившись тут, шукаючи спосіб встановити умову в заяві про
розробника

5
split - це невдалий опис цієї операції, оскільки вона вже має специфічне значення щодо рядків Python. Я думаю, що розділення - це більш точне (або принаймні менш перевантажене в контексті ітерабелів Python) для опису цієї операції. Я приземлився тут, шукаючи еквівалент списку str.split(), щоб розділити його на упорядковану колекцію послідовних підсписів. Наприклад split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6]), на відміну від поділу елементів списку на категорії.
Тушковане

Обговорення тієї ж теми в python-list.
Xiong Chiamiov

IMAGE_TYPES повинен бути набір замість кортежу: IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (1) замість n (o / 2), практично не має різниці в читанні.
ChaimG

Відповіді:


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

чи є більш елегантний спосіб це зробити?

Цей код ідеально читабельний і надзвичайно зрозумілий!

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

Знову ж, це добре!

Можливо, незначні покращення продуктивності використання наборів, але це банальна різниця, і я вважаю, що розуміння списку набагато простіше читати, і вам не доведеться турбуватися про те, що замовлення буде зіпсовано, а дублікати видаляються тощо.

Насправді я можу зробити ще один крок "назад" і просто використовувати простий цикл:

images, anims = [], []

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

Зрозуміння списку чи використання set()нормально, поки вам не потрібно буде додати якусь іншу перевірку чи інший біт логіки - скажімо, ви хочете видалити всі 0-байтні jpeg, просто додайте щось на кшталт ..

if f[1] == 0:
    continue

44
Хіба не існує способу розуміння списку без того, щоб двічі перебирати список?
balki

35
Проблема в тому, що це порушує принцип DRY. Було б добре, якби був кращий спосіб зробити це.
Сурма

21
Як тільки апетит до функціонального програмування (Haskell) або функціонального стилю (LINQ) підвищений, ми починаємо пахнути Python за його віком - [x for x in blah if ...]- багатослівний, lambdaнезграбний і обмежений ... Це відчуття, ніби водіння найкрутішої машини з 1995 року сьогодні. Не те саме, що тоді.
Томаш Гандор

6
@TomaszGandor FTR, Haskell старший за Python (і фактично вплинув на його дизайн). Я думаю, що синтаксис для розуміння списків та лямбданів навмисно тримався трохи на багатослівній стороні, можливо, щоб відмовити над їх використанням. Що насправді є чималим ризиком ... наскільки мені подобається Haskell, я можу зрозуміти, чому багато людей вважають Python взагалі більш читабельним.
приблизно приблизно

4
найпростіший спосіб зробити це просто для циклу ... Один цикл, дуже зрозумілий і читабельний
Anentropic

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

14
Це неймовірно геніально! Мені знадобився певний час, щоб зрозуміти, що відбувається. Я хотів би знати, чи думають інші, що це можна вважати читабельним кодом чи ні.
jgpaiva

171
good.append(x) if x in goodvals else bad.append(x)є більш читабельним.
дансальмо

21
@dansalmo Тим більше , що ви можете зробити його однострочнікі з для циклу, і якщо ви хочете додати що - то більш складне , ніж x, ви можете зробити це в один appendтільки:for x in mylist: (good if isgood(x) else bad).append(x)
йо »

2
@MLister, у цьому випадку ви, ймовірно, повинні включити пошук атрибутів(bad.append, good.append)
Джон Ла Рой,

11
Трохи коротша варіація:(good if x in goodvals else bad).append(x)
Pi Delport

104

Ось лінивий підхід ітератора:

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)

Він оцінює умову один раз на елемент і повертає два генератори, перший виводить значення з послідовності, де умова є істинним, а інший - де неправдивим.

Оскільки це лінь, ви можете використовувати його на будь-якому ітераторі, навіть нескінченному:

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

Зазвичай, хоч краще підхід до повернення списку лінивих:

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

Редагувати. Для більш конкретного використання розділення елементів за різними списками за допомогою клавіші є загальна функція, яка робить це:

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

Використання:

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

Ви, напевно, маєте рацію, що це порушує принцип YAGNI. Він ґрунтується на припущенні, що в майбутньому кількість різних списків, на які можна розділити речі, зростатиме.
Мурахи Аасма

17
Це може бути багато коду, але якщо [ x for x in my_list if ExpensiveOperation(x) ]запускати багато часу, ви, звичайно, не хочете це робити двічі!
dash-tom-bang

1
+1 для пропонування декількох варіантів, включаючи ітератор на основі і специфічне рішення "в X". ОП "в хорошій мірі" може бути невеликим, але замінити це дуже великим словником або дорогим предикатом може бути дорого. Крім того, це зменшує необхідність двічі писати розуміння списку скрізь, де це потрібно, тим самим зменшуючи ймовірність введення помилок друку / користувача. Приємне рішення. Дякую!
cod3monk3y

3
Зауважте, що teeзберігаються всі значення між ітераторами, які він повертає, тому він не дійсно заощадить пам'ять, якщо ви переймете один генератор, а потім інший.
Джон Ла Руй

25

Проблема з усіма запропонованими рішеннями полягає в тому, що воно буде перевіряти та застосовувати функцію фільтрації двічі. Я зробив би просту маленьку функцію, як це:

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

Таким чином ви нічого не обробляєте двічі, а також не повторюєте код.


Я згоден. Я шукав «елегантний» (тобто тут мається на увазі короткий та вбудований / неявний) спосіб зробити це без сканування списку двічі, але це, здається, (без профілювання) - це шлях. Звичайно, це було б важливо лише для великих обсягів даних.
Меттью Флашен

IMHO, якщо ви знаєте спосіб зробити це з меншим використанням процесора (і, таким чином, з меншим витратою енергії), немає причин не використовувати його.
winden

2
@winden ... Перенесення всього мого Python на C.;)
Елліот Кемерон

19

Моє взяти на це. Я пропоную ледачий однопрохідний,partition функцію, яка зберігає відносний порядок у вихідних подальшах.

1. Вимоги

Я припускаю, що вимоги:

  • підтримувати відносний порядок елементів (отже, без наборів та словників)
  • оцінюйте умову лише один раз для кожного елемента (отже, не використовуючи ( i) filterабо groupby)
  • допускати ліниве споживання будь-якої послідовності (якщо ми можемо дозволити їх попередньо обчислити, то наївна реалізація, ймовірно, також буде прийнятною)

2. splitбібліотека

Моя partitionфункція (представлена ​​нижче) та інші подібні функції перетворили її на невелику бібліотеку:

Його можна встановити звичайно через PyPI:

pip install --user split

Щоб розділити базу списку за умовою, використовуйте partitionфункцію:

>>> 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. partitionпояснюється функція

Внутрішньо нам потрібно створити дві послідовності одночасно, тому споживання лише однієї послідовності виведення змусить і другу обчислити. І нам потрібно підтримувати стан між запитами користувачів (зберігати оброблені, але ще не запитувані елементи). Для збереження стану я використовую дві подвійні черги ( deques):

from collections import deque

SplitSeq клас піклується про ведення господарства:

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

Магія буває в її .getNext()методі. Це майже як .next() ітератори, але дозволяє вказати, який саме елемент ми хочемо цього разу. За сценою він не відкидає відхилені елементи, а замість цього ставить їх в одну з двох черг:

    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)

Кінцевий користувач повинен використовувати partitionфункцію. Вона приймає функцію умови і послідовність (так само , як mapі filter), і повертає два генератора. Перший генератор будує послідовність елементів, для яких виконується умова, другий будує додаткову підпорядкованість. Ітератори та генератори дозволяють ледаче розщеплювати навіть довгі чи нескінченні послідовності.

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

Я вибрав тестову функцію як перший аргумент для спрощення часткового застосування в майбутньому (подібно до того, як mapі filter як функція тесту є першим аргументом).


15

Мені в основному подобається підхід Андерса, оскільки він дуже загальний. Ось версія, яка ставить категоризатор першим (щоб відповідати синтаксису фільтра) і використовує вирок за замовчуванням (передбачається, імпортується).

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

Я збирався спробувати відібрати заяви із дзен Python, які застосовуються тут, але це занадто багато для коментаря. =) Чудовий фрагмент коду.
jpmc26

13

Перший перехід (попередня редагування): використання наборів:

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]

Це добре як для читабельності (IMHO), так і для продуктивності.

Другий піти (після редагування після запуску):

Створіть свій список хороших розширень як набір:

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

і це збільшить продуктивність. Інакше те, що у вас є, мені добре виглядає.


4
не найкраще рішення, якщо списки були в певному порядку перед розбиттям і вам потрібно, щоб вони залишалися в такому порядку.
Даніяр

8
Хіба це не видалить дублікати?
mavnn

Створення набору - O (n log n). Ітерація списку двічі - O (n). Набір рішення може бути більш елегантним (коли це правильне в першу чергу), але, звичайно, повільніше, оскільки n збільшується.
dash-tom-bang

1
@ dash-tom-bang Ітерація списку є O (n * n). Це тому, що кожен елемент у списку, можливо, потрібно буде порівнювати з кожним елементом у списку goodvals.
ChaimG

@ChaimG хороший момент, хоча нам також потрібно враховувати вартість операцій перехрестя та різниці (що я не знаю, з іншого боку, але я впевнений, що вони також суперлінійні).
dash-tom-bang

10

itertools.groupby майже робить те, що ви хочете, за винятком того, що вимагає сортування елементів, щоб забезпечити отримання єдиного суміжного діапазону, тому вам потрібно спершу сортувати за вашим ключем (інакше ви отримаєте кілька переплетених груп для кожного типу). напр.

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)

дає:

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

Як і в інших рішеннях, ключовий функція може бути визначений для поділу на будь-яку кількість бажаних груп.


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

Ця елегантна і лаконічна відповідь @dansalmo виявилася похованою в коментарях, тому я просто повторюю це як відповідь, щоб він міг отримати відомість, яку вона заслуговує, особливо для нових читачів.

Повний приклад:

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

5

Якщо ви хочете зробити його у стилі FP:

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

Не найчитабельніше рішення, але принаймні повторюється через мій список лише один раз.


1
Хоча вона повторюється через список лише один раз, продуктивність не настільки хороша, оскільки список додається. Додавання до списку - це потенційно дорога операція (порівняно, наприклад, з deque.append, наприклад). Насправді це рішення надзвичайно повільне порівняно з іншими рішеннями тут (21,4 з 100000 випадкових цілих чисел і тестування їх значення).
rlat

5

Особисто мені подобається версія, яку ви цитували, якщо припустити, що у вас вже є список, який ви бачите goodvals. Якщо ні, то на кшталт:

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

Звичайно, це дійсно дуже схоже на використання розуміння списку, як ви робили раніше, але з функцією замість пошуку:

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

Взагалі, я вважаю естетику розуміння списку дуже приємною. Звичайно, якщо вам насправді не потрібно зберігати замовлення та не потребуєте дублікатів, використання методів intersectionі differenceнаборів також буде добре.


Звичайно, filter(lambda x: is_good(x), mylist)можна зменшити доfilter(is_good, mylist)
грудня о 14:26

додавання додаткового виклику функції насправді подвоює (!) час виконання, порівняно зі розумінням списку, з того, що я бачив у профілюванні. важко перемогти розуміння списку, більшість часу.
Корлі Бригман

4

Я думаю, що узагальнення поділу ітерабельного на основі N умов зручне

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

Наприклад:

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]

Якщо елемент може задовольнити декілька умов, усуньте розрив.


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)

Перевірте це


3

Іноді, схоже, розуміння списку не найкраще використовувати!

Я зробив невеликий тест на основі відповіді, яку люди дали на цю тему, перевіреної у випадково створеному списку. Ось генерація списку (мабуть, кращий спосіб зробити це, але справа не в цьому):

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

А ось і ми

# 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 найкращим результатом є відповідь 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%  --

Швидші функції з оновленими орієнтирами тут .
ChaimG

2

Ще одне рішення цієї проблеми. Мені потрібно було якнайшвидше рішення. Це означає лише одну ітерацію над списком і, переважно, O (1) для додавання даних до одного із результуючих списків. Це дуже схоже на рішення, яке надає зустріч , за винятком набагато коротших:

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

Потім ви можете використовувати функцію наступним чином:

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

Якщо ви не в порядку з результуючим dequeоб'єктом, ви можете легко перетворити його list, setвсе, що ви , як (наприклад ,list(lower) ). Перетворення відбувається набагато швидше, це побудова списків безпосередньо.

Цей спосіб підтримує порядок елементів, а також будь-яких дублікатів.


2

Наприклад, розділення списку на парні та непарні

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

Або взагалі:

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

Переваги:

  • Найкоротший можливий спосіб
  • Присудок застосовується лише один раз для кожного елемента

Недоліки

  • Потрібні знання парадигми функціонального програмування

2

Натхненний чудовою (але лаконічною) відповіддю @ gnibbler , ми можемо застосувати такий підхід до відображення кількох розділів:

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

Потім splitterїх можна використовувати наступним чином:

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

Це працює для більш ніж двох розділів зі складнішим відображенням (і на ітераторах):

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

Або за допомогою словника на карті:

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

... щойно помітив, це в основному те саме, що @ alan-isaac вже відповів.
Джош Боде

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

додаток повертає None, тому воно працює.


1

Для виступу, спробуйте itertools.

Модуль itertools стандартизує основний набір швидких, ефективних у пам'яті інструментів, які корисні самі по собі або в поєднанні. Разом вони утворюють «ітераторну алгебру», що дозволяє чітко та ефективно сконструювати спеціалізовані інструменти на чистому Python.

Див. Itertools.ifilter або imp .

itertools.ifilter (предикат, ітерабельний)

Зробіть ітератор, який фільтрує елементи з ітерабельного, повертаючи лише ті, для яких присудок є істинним


ifilter / imap (і генератори взагалі) досить повільні ... взагалі, в моєму профілюванні, якщо взяти розуміння списку, як [x for x in a if x > 50000]на простому масиві 100000 цілих чисел (через random.shuffle), filter(lambda x: x> 50000, a)це займе 2х разів , ifilter(lambda x: x> 50000, a); list(result)триває приблизно в 2,3 рази. Дивно, але правда.
Корлі Бригман

1

Іноді вам не знадобиться інша половина списку. Наприклад:

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

Це найшвидший спосіб.

Він використовує if else(як відповідь dbr), але створює набір спочатку. Набір зменшує кількість операцій з O (m * n) до O (log m) + O (n), в результаті чого швидкість на 45% +.

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)

Трохи коротше:

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)

Результати порівняння:

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 (модифікований від 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

Якщо ви наполягаєте на розумному, ви можете прийняти рішення Віндена і лише трохи хибної кмітливості:

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

3
"D або {}" трохи небезпечно. Якщо введено порожній дікт, він не буде вимкнено на місці.
Брайан

Щоправда, але це повертається, тому ... Насправді, це ідеальний приклад того, чому ви не хочете додавати більш розумний у свій код. :-P
Андерс Евреній

0

Тут уже досить багато рішень, але ще один спосіб зробити це -

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

Iterates над списком лише один раз, і виглядає трохи пітонічнішим і, отже, читабельним для мене.

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

Я б застосував 2-прохідний підхід, відокремивши оцінку присудка від фільтрації списку:

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

Що приємно в цьому, ефективність роботи (крім того, щоб оцінювати predлише один раз кожного учасникаiterable ), полягає в тому, що вона переміщує багато логіки з інтерпретатора і перетворюється на високооптимізований код ітерації та картографування. Це може прискорити ітерацію довгих ітерабелів, як описано у цій відповіді .

Експресивність залежить від виразних ідіом, таких як розуміння та відображення.


0

рішення

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

тест

[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

Якщо ви не заперечуєте проти використання зовнішньої бібліотеки, я знаю два, що натільно реалізують цю операцію:

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

Не впевнений, що це хороший підхід, але це може бути зроблено і таким чином

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

Якщо список складається з груп і переривчастих роздільників, ви можете використовувати:

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

Використання:

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]

Приємно, коли стан довший, як, наприклад, у вашому прикладі. Читач не повинен з'ясувати негативний стан та чи фіксує він всі інші випадки.


0

Ще одна відповідь, коротка, але "зла" (для побічних ефектів для розуміння списку).

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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.