Python: разделить список на основе условия? - PullRequest
227 голосов
/ 04 июня 2009

Как лучше, как с эстетической точки зрения, так и с точки зрения производительности, разделить список элементов на несколько списков на основе условного обозначения? Эквивалент:

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]

Ответы [ 29 ]

0 голосов
/ 05 марта 2017

Мой любимый рецепт для этого:

goodvals = set(goodvals)    # Turbocharges the performance by 55%!  
good, bad = [], []
_ = [good.append(x) if x in goodvals else bad.append(x) for x in mylist]

Простой, быстрый и читаемый; каким Python должен был быть.

  • Делая goodvals в set (который использует хеш-таблицу) вместо tuple, мы получаем супер быстрый поиск.
  • Каждый элемент в mylist проверяется только один раз. это помогает сделать это быстрее.
  • _ = - это Pythonic способ заявить, что мы намеренно отбрасываем результат понимания списка. Это не ошибка.

(На основании комментария Дансалмо к этого ответа, потому что он, похоже, заслуживает своего собственного ответа.)

РЕДАКТИРОВАТЬ:

Преобразование goodvals в установленную производительность турбокомпрессоров на 55% по моему тесту. Использование tuple - это O (n * m), а преобразование его в set - это O (log n + m).

Кроме того, goodvals, (т.е. n), имеет длину всего пять элементов. mylist, (т. Е. М), может иметь сотни предметов. Кроме того, создание набора, вероятно, высоко оптимизировано под капотом в коде языка C.

Вот код теста, который я использовал. Он основан на коде, взятом из этого ответа и изменен для работы с Python v3.7.0, работающим в Windows 7.

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 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 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 f7(*_):
    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 f8(*_):
    good, bad = [], []
    good_list_set = set(good_list)
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list_set 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("%5.0f%%" % (results[f]/results[func]*100 - 100))
        diffs = " ".join(diff)

        print("%s\t%6d/s %s" % (f, results[f], diffs))


if __name__=='__main__':
    from timeit import Timer
cmpthese(1000, 'f1 f2 f3 f5 f6 f7 f8'.split(" "))
0 голосов
/ 12 октября 2017

Не уверен, что это хороший подход, но это можно сделать и таким способом

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 голосов
/ 04 июня 2009

Если вы настаиваете на умном, вы могли бы принять решение Виндена и немного поддельную хитрость:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d
0 голосов
/ 19 декабря 2016

Если вы не возражаете против использования внешней библиотеки, я знаю, что наивно реализуют эту операцию:

>>> 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 голосов
/ 05 мая 2012

Если вы не хотите использовать две строки кода для операции, семантика которой требуется только после того, как вы просто заключите некоторые из вышеперечисленных подходов (даже ваших собственных) в одну функцию:

def part_with_predicate(l, pred):
    return [i for i in l if pred(i)], [i for i in l if not pred(i)]

Это не ленивый подход, он повторяется дважды по списку, но он позволяет разбить список на одну строку кода.

0 голосов
/ 10 декабря 2013
def partition(pred, seq):
  return reduce( lambda (yes, no), x: (yes+[x], no) if pred(x) else (yes, no+[x]), seq, ([], []) )
0 голосов
/ 28 апреля 2015

решение

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 голосов
/ 08 марта 2014

Уже довольно много решений здесь, но еще один способ сделать это -

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

Итерирует по списку только один раз и выглядит немного более питоническим и, следовательно, читаемым для меня.

>>> 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 голосов
/ 17 декабря 2014

Я бы выбрал двухпроходный подход, отделяющий оценку предиката от фильтрации списка:

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), это то, что он перемещает много логики из интерпретатора в высоко оптимизированную итерацию и отображение код. Это может ускорить итерацию на длинных итерациях, как описано в этом ответе .

В плане выразительности он использует такие выразительные идиомы, как понимание и отображение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...