Эквивалент python для фильтра(), получающий два выходных списка (т.е. раздел списка)

Скажем, у меня есть список и функция фильтрации. Используя что-то вроде

>>> filter(lambda x: x > 10, [1,4,12,7,42])
[12, 42]

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

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

Ответ 1

Попробуйте следующее:

def partition(pred, iterable):
    trues = []
    falses = []
    for item in iterable:
        if pred(item):
            trues.append(item)
        else:
            falses.append(item)
    return trues, falses

Использование:

>>> trues, falses = partition(lambda x: x > 10, [1,4,12,7,42])
>>> trues
[12, 42]
>>> falses
[1, 4, 7]

В itertools recipes есть предложение реализации:

from itertools import filterfalse, tee

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)

Рецепт из документации Python 3.x. В Python 2.x filterfalse называется ifilterfalse.

Ответ 2

>>> def partition(l, p):
...     return reduce(lambda x, y: (x[0]+[y], x[1]) if p(y) else (x[0], x[1]+[y]), l,  ([], []))
... 
>>> partition([1, 2, 3, 4, 5], lambda x: x < 3)
([1, 2], [3, 4, 5])

и немного более уродливую, но более быструю версию вышеуказанного кода:

def partition(l, p):
    return reduce(lambda x, y: x[0].append(y) or x if p(y) else x[1].append(y) or x, l,  ([], []))

Это второе редактирование, но я думаю, что это важно:

 def partition(l, p):
     return reduce(lambda x, y: x[not p(y)].append(y) or x, l, ([], []))

Второй и третий такие же быстрые, как итеративный, но меньше кода.

Ответ 3

Я думаю, что groupby может быть более уместным здесь:

http://docs.python.org/library/itertools.html#itertools.groupby

Например, разбиение списка на нечетные и четные числа (или может быть произвольным числом групп):

>>> l=range(6)
>>> key=lambda x: x % 2 == 0
>>> from itertools import groupby
>>> {k:list(g) for k,g in groupby(sorted(l,key=key),key=key)}
    {False: [1, 3, 5], True: [0, 2, 4]}

Ответ 4

Если у вас нет дублирующего элемента в вашем списке, вы можете определенно использовать set:

>>> a = [1,4,12,7,42]
>>> b = filter(lambda x: x > 10, [1,4,12,7,42])
>>> no_b = set(a) - set(b)
set([1, 4, 7])

или вы можете сделать список по понятным:

>>> no_b = [i for i in a if i not in b]

N.B: это не функция, а просто знание первого результата fitler(), вы можете вывести элемент, который не сильно повлиял на ваш критерий фильтра.

Ответ 5

from itertools import ifilterfalse

def filter2(predicate, iterable):
    return filter(predicate, iterable), list(ifilterfalse(predicate, iterable))

Ответ 6

У меня точно было это требование. Я не увлекаюсь рецептом itertools, так как он включает в себя два отдельных прохода через данные. Здесь моя реализация:

def filter_twoway(test, data):
    "Like filter(), but returns the passes AND the fails as two separate lists"
    collected = {True: [], False: []}
    for datum in data:
        collected[test(datum)].append(datum)
    return (collected[True], collected[False])

Ответ 7

TL; DR

принятый, самый проголосовавший ответ [1] от Марк Байерс

def partition(pred, iterable):
    trues = []
    falses = []
    for item in iterable:
        if pred(item):
            trues.append(item)
        else:
            falses.append(item)
    return trues, falses

является самым простым и быстрее всего.

Сравнительный анализ различных подходов

Различные подходы, которые были предложены, могут быть классифицированы в целом в трех категориях,

  • прямое манипулирование списком через lis.append, возвращая 2-кортеж списков,
  • lis.append, опосредуемый функциональным подходом, возвращающий 2-кортеж списков,
  • с использованием канонического рецепта, указанного в itertools fine документация, возвращающая 2-х кортеж, свободно говорящих, генераторов.

Здесь следует ванильная реализация трех методов, первая функциональный подход, то itertools и, в конце концов, два разных реализации прямого манипулирования списком, альтернатива использование False равно нулю, True - это один трюк.

Обратите внимание, что это Python3 - следовательно reduce происходит от functools - и что OP запросит кортеж вроде (positives, negatives), но мой реализация возвращает (negatives, positives)...

$ ipython
Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 13:51:32) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import functools
   ...: 
   ...: def partition_fu(p, l, r=functools.reduce):
   ...:     return r(lambda x, y: x[p(y)].append(y) or x, l, ([], []))
   ...: 

In [2]: import itertools
   ...: 
   ...: def partition_it(pred, iterable,
   ...:               filterfalse=itertools.filterfalse,
   ...:               tee=itertools.tee):
   ...:     t1, t2 = tee(iterable)
   ...:     return filterfalse(pred, t1), filter(pred, t2)
   ...: 

In [3]: def partition_li(p, l):
   ...:     a, b = [], []
   ...:     for n in l:
   ...:         if p(n):
   ...:             b.append(n)
   ...:         else:
   ...:             a.append(n)
   ...:     return a, b
   ...: 

In [4]: def partition_li_alt(p, l):
   ...:     x = [], []
   ...:     for n in l: x[p(n)].append(n)
   ...:     return x
   ...: 

Нам нужен предикат для применения к нашим спискам и спискам (опять же, свободно говорящий), на котором можно работать.

In [5]: p = lambda n:n%2

In [6]: five, ten = range(50000), range(100000)

Чтобы преодолеть проблему при тестировании подхода itertools, это было сообщается joeln на 31 окт 13 в 6:17

Глупости. Вы рассчитали время, затраченное на создание генераторы в filterfalse и filter, но вы не повторили через вход или вызванный pred один раз! Преимущество itertools рецепт состоит в том, что он не материализует какой-либо список или выглядит далее вперед на входе, чем необходимо. Он вызывает pred дважды, как часто и занимает почти в два раза больше, чем Байерс и др.

Я думал о цикле пустот, который просто создает все пары элементов в двух итерабелях, возвращаемых разным разделом функции.

Сначала мы используем два фиксированных списка, чтобы иметь представление о перегрузка подразумевает (используя очень удобную магию IPython %timeit)

In [7]: %timeit for e, o in zip(five, five): pass
4.21 ms ± 39.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Далее мы используем разные реализации, один за другим

In [8]: %timeit for e, o in zip(*partition_fu(p, ten)): pass
53.9 ms ± 112 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit for e, o in zip(*partition_it(p, ten)): pass
44.5 ms ± 3.84 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [10]: %timeit for e, o in zip(*partition_li(p, ten)): pass
36.3 ms ± 101 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [11]: %timeit for e, o in zip(*partition_li_alt(p, ten)): pass
37.3 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [12]:

Комментарии

Самый простой из подходов также является самым быстрым.

Использование тэга x[p(n)], ehm, бесполезно, потому что на каждом шагу вы должны индексировать структуру данных, давая вам небольшое наказание - это однако приятно знать, хотите ли вы убедить выжившего в спаде культура при pythonizing.

Функциональный подход, который оперативно эквивалентен альтернативная реализация append, на 50% медленнее, возможно, из-за тот факт, что у нас есть дополнительная функция (w/r для предикации оценки) вызов для каждого элемента списка.

Подход itertools имеет (обычные) преимущества: ❶ нет потенциально большой список создается и ❷ список входных данных не является полностью обрабатывается, если вы выходите из цикла потребления, но когда мы использовать его медленнее из-за необходимости применять предикат на обоих концы tee

Помимо

Я влюбился в идиому object.mutate() or object, которая был представлен Marii в их ответе функциональный подход к проблеме - я боюсь, что рано или поздно, Я буду злоупотреблять им.

Сноски

[1] Принято и проголосовано сегодня, 14 сентября 2017 года, но, конечно, я возлагаю большие надежды на этот ответ!

Ответ 8

Кажется, что все думают, что их решение является лучшим, поэтому я решил использовать timeit для тестирования всех них. Я использовал "def is_odd (x): return x и 1" в качестве моей предикатной функции, а "xrange (1000)" - как итеративный. Вот моя версия Python:

Python 2.7.3 (v2.7.3:70274d53c1dd, Apr  9 2012, 20:52:43) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin

И вот результаты моего тестирования:

Mark Byers
1000 loops, best of 3: 325 usec per loop

cldy
1000 loops, best of 3: 1.96 msec per loop

Dan S
1000 loops, best of 3: 412 usec per loop

TTimo
1000 loops, best of 3: 503 usec per loop

Все они сопоставимы друг с другом. Теперь попробуйте использовать пример, указанный в документации Python.

import itertools

def partition(pred, iterable,
              # Optimized by replacing global lookups with local variables
              # defined as default values.
              filter=itertools.ifilter,
              filterfalse=itertools.ifilterfalse,
              tee=itertools.tee):
    '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)

Кажется, это немного быстрее.

100000 loops, best of 3: 2.58 usec per loop

Код примера itertools превосходит всех желающих в размере не менее 100! Мораль - не переделывать колесо.

Ответ 9

Вы можете посмотреть django.utils.functional.partition решение:

def partition(predicate, values):
    """
    Splits the values into two sets, based on the return value of the function
    (True/False). e.g.:

        >>> partition(lambda x: x > 3, range(5))
        [0, 1, 2, 3], [4]
    """
    results = ([], [])
    for item in values:
        results[predicate(item)].append(item)
    return results

По-моему, это самое элегантное решение, представленное здесь.

Эта часть не документирована, только исходный код можно найти на https://docs.djangoproject.com/en/dev/_modules/django/utils/functional/

Ответ 10

Уже много хороших ответов. Мне нравится использовать это:

def partition( pred, iterable ):
    def _dispatch( ret, v ):
        if ( pred( v ) ):
            ret[0].append( v )
        else:
            ret[1].append( v )
        return ret
    return reduce( _dispatch, iterable, ( [], [] ) )

if ( __name__ == '__main__' ):
    import random
    seq = range( 20 )
    random.shuffle( seq )
    print( seq )
    print( partition( lambda v : v > 10, seq ) )