Python: разделить список на основе условия?

273

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

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]
Parand
источник
5
приземлился здесь, ища способ иметь условие в заявлении строителя множеств, ваш вопрос ответил на мой вопрос :)
Anuvrat Parashar
5
split - неудачное описание этой операции, поскольку она уже имеет особое значение по отношению к строкам Python. Я думаю, что разделение - более точное (или, по крайней мере, менее перегруженное в контексте итераций Python) слово для описания этой операции. Я приземлился здесь в поисках эквивалентного списка str.split(), чтобы разбить список на упорядоченную коллекцию последовательных подсписков. Например split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6]), в отличие от деления элементов списка по категориям.
Тушеное мясо
Обсуждение этой же темы в списке python.
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]

Опять же, это хорошо!

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

На самом деле, я могу сделать еще один шаг назад и просто использовать простой цикл for:

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
DBR
источник
44
Разве нет способа понимания списка без необходимости повторения списка дважды?
Балки
35
Проблема в том, что это нарушает принцип СУХОГО. Было бы хорошо, если бы был лучший способ сделать это.
Сурьма
21
Когда аппетит к функциональному программированию (Haskell) или к функциональному стилю (LINQ) повышается, мы начинаем ощущать запах Python для его возраста [x for x in blah if ...]- многословный, lambdaнеуклюжий и ограниченный ... Это похоже на вождение самой крутой машины с 1995 года сегодня. Не так, как тогда.
Томаш Гандор
6
@TomaszGandor FTR, Haskell старше Python (и фактически повлиял на его дизайн). Я думаю, что синтаксис для понимания списков и лямбда-кодов был намеренно сохранен на многословной стороне, возможно, чтобы воспрепятствовать их чрезмерному использованию. Это действительно немного рискованно ... насколько мне нравится Haskell, я понимаю, почему многие люди находят Python в целом более читабельным.
оставлено около
4
простой цикл for - лучший способ сделать это ... один цикл, очень четкий и читаемый
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 Тем более, что вы можете сделать его однострочным с циклом for, и если вы хотите добавить что-то более сложное, чем x, вы можете сделать это appendтолько одним :for x in mylist: (good if isgood(x) else bad).append(x)
yo '
2
@MLister, в этом случае вам, вероятно, следует включить поиск атрибутов(bad.append, good.append)
John La Rooy
11
Немного более короткий вариант:(good if x in goodvals else bad).append(x)
Пи Дельпорт
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']
Муравьи Аасма
источник
Вы, вероятно, правы, что это нарушает принцип ЯГНИ. Он основан на предположении, что в будущем число различных списков, на которые можно разделить объекты, будет расти.
Муравьи Аасма
17
Это может быть много кода, но если [ x for x in my_list if ExpensiveOperation(x) ]запуск занимает много времени, вы, конечно, не хотите делать это дважды!
Даш-Том-Банг
1
+1 за предложение нескольких вариантов, в том числе на основе итераторов и конкретного решения "in X". OP в «goodvals» может быть небольшим, но замена его на очень большой словарь или дорогой предикат может быть дорогой. Кроме того, это уменьшает необходимость писать списки в два раза везде, где это необходимо, что снижает вероятность появления опечаток / ошибок пользователя. Хорошее решение. Спасибо!
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)

Таким образом, вы ничего не обрабатываете дважды, а также не повторяете код.

Winden
источник
Я согласен. Я искал «элегантный» (то есть здесь означает «короткий» и «встроенный / неявный») способ сделать это, не сканируя список дважды, но это, кажется (без профилирования), путь. Конечно, это будет иметь значение только для больших объемов данных.
Мэтью Флэшен
ИМХО, если вы знаете способ сделать это с меньшим использованием процессора (и, следовательно, с меньшим энергопотреблением), нет никаких причин не использовать его.
Винден
2
@winden ... Портирование всего моего Питона на 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 иметь тестовую функцию в качестве первого аргумента).

sastanin
источник
15

Мне в основном нравится подход Андерса, так как он очень общий. Вот версия, которая ставит классификатор на первое место (для соответствия синтаксису фильтра) и использует defaultdict (предполагается, импортированный).

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
Алан Исаак
источник
Я собирался выбрать утверждения из Zen of 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), так и для производительности.

Второй ход (после OP-редактирования):

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

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

и это повысит производительность. В противном случае, то, что у вас есть, выглядит хорошо для меня.

RichieHindle
источник
4
не лучшее решение, если списки были в некотором порядке до разделения, и вам нужно, чтобы они оставались в таком порядке.
Данияр
8
Разве это не удалит дубликаты?
Mavnn
Создание набора - это O (n log n). Повторение списка дважды - O (n). Заданное решение может быть более элегантным (если оно в первую очередь корректно), но, безусловно, медленнее при увеличении n.
Даш-Том-Банг
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)) ]

Не самое читаемое решение, но, по крайней мере, перебирает mylist только один раз.

michau
источник
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)
Робру
добавление дополнительного вызова функции фактически удваивает (!) время выполнения, по сравнению со списками, из того, что я видел в профилировании. Трудно превзойти понимание списка, большую часть времени.
Корли Бригман
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)

Проверьте это

Hanfei Sun
источник
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%  --
FunkySayu
источник
Более быстрые функции с обновленными тестами здесь .
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) ). Преобразование происходит намного быстрее, чем построение списков напрямую.

Этот метод сохраняет порядок элементов, а также любые дубликаты.

rlat
источник
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)]

append возвращает None, так что это работает.

Бига
источник
1

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

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

Смотрите itertools.ifilter или imap.

itertools.ifilter (предикат, повторяемый)

Создайте итератор, который отфильтровывает элементы от повторяемых, возвращая только те, для которых предикат равен True

гимель
источник
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(" "))
ChaimG
источник
0

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

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d
Андерс Эурениус
источник
3
"D or {}" немного опасно. Если передается пустой dict, он не будет видоизменен на месте.
Брайан
Да, но это возвращается, так что ... На самом деле, это прекрасный пример того, почему вы не хотите добавлять более умный код. :-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)]

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

>>> 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
источник
0

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

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])
doctorzeb8
источник
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')]
MSeifert
источник
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]

Приятно, когда условие дольше, как в вашем примере. Читатель не должен выяснять отрицательное условие и охватывает ли он все другие случаи.

Chrisjan
источник
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]
фаэтон
источник