Эквивалент 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 ) )