Python 等效于 filter()获取两个输出列表(即列表的分区)

假设我有一个列表和一个过滤函数

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

我可以得到符合条件的元素。有没有一个函数我可以用来输出两个列表,一个匹配的元素,一个剩余的元素?我可以调用 filter()函数两次,但这有点难看:)

编辑: 元素的顺序应该是保守的,并且我可能有多次相同的元素。

27253 次浏览

试试这个:

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 食谱还提出了一项执行建议:

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

如果你的列表中没有重复的元素,你完全可以使用 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]

注意: 这不是一个函数,只要知道第一个 fitler ()结果,就可以推导出没有过滤条件的元素。

from itertools import ifilterfalse


def filter2(predicate, iterable):
return filter(predicate, iterable), list(ifilterfalse(predicate, iterable))
>>> 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, ([], []))

第二个和第三个代码与上一个迭代代码一样快,但代码更少。

我觉得 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]}

已经有很多好的答案了,我喜欢用这个:

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

我就是有这个要求。我并不热衷于 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])

每个人似乎都认为他们的解决方案是最好的,所以我决定用时间来测试他们所有人。我使用“ 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倍的优势击败了所有参与者!教训是,不要总是重复发明轮子。

你可以看看 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/上找到

DR

接受,投票最多的答案[1] by 马克 · 拜尔斯

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

是最简单的 最快的。

对不同的方法进行基准测试

所提出的不同方法可以分类 大致分为三类,

  1. 通过 lis.append进行简单的列表操作,返回一个2元组 名单,
  2. lis.append通过功能方法介导,返回一个2元组 名单,
  3. 使用 itertools罚款中给出的规范配方 文档,返回一个2元组的,宽泛地说,生成器。

下面首先介绍这三种技术的一般实现 函数的方法,然后 itertools和最终两个不同的 直接列表操作的实现,另一种方法是 使用 False是零,True是一个技巧。

注意,这是 Python 3ーー因此 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
...:

我们需要一个谓词来应用到我们的列表和列表中(同样是松散的) speaking) on which to operate.

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


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

为了克服测试 itertools方法中的问题,这是 由 Joel报道 10月31日13时6分17秒

胡说。您已经计算了构造 filterfalsefilter中的生成器,但是没有迭代 通过输入或调用 pred一次 itertools的配方是,它不具体化任何列表,或看 它调用 pred两次 几乎是拜尔斯等人的两倍。

我想到了一个可以实例化所有夫妻的 void 循环 由不同分区返回的两个迭代中的元素 功能。

首先,我们使用两个固定列表来了解 隐含的重载(使用非常方便的 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)]技巧是没有用的,因为在每个步骤中 必须对数据结构进行索引,这会给你带来 轻微的的损失 不管你想不想说服一个衰落的幸存者 在蟒蛇文化。

函数方法,即 手术等效于 可选的 append实现比较慢约50% ,可能是由于 事实上,我们有一个额外的(w/r 谓词求值)函数 调用每个列表元素。

itertools方法具有(通常的)优点: something no 可能很大的列表被实例化,而输入列表没有被实例化 完全处理,如果你打破了消费者循环,但当我们 使用它会比较慢,因为需要对两者都应用谓词 tee的末端

让开

我爱上了 object.mutate() or object的成语 被 玛丽暴露了 在 他们的回答中显示 解决这个问题的实用方法ーー恐怕迟早, 我要滥用它。

脚注

[1]2017年9月14日,也就是今天,我被接受并被投票最多ーー但我当然对我的这个答案抱有最高的期望!

附加到目标列表的简明代码

    def partition(cond,inputList):
a,b= [],[]
for item in inputList:
target = a if cond(item) else b
target.append(item)
return a, b




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

现有的解决方案要么将一个迭代分割成两个 清单,要么低效地将它分割成两个生成器。这里有一个实现,可以有效地将一个迭代分割成两个 发电机,也就是说,对于迭代中的每个元素,谓词函数最多只调用一次。您可能想要使用这个版本的一个实例是,如果您需要用计算谓词的开销来划分一个非常大(甚至无限大)的可迭代项。

from collections import deque


def partition(pred, iterable):
seq = iter(iterable)
true_buffer = deque()
false_buffer = deque()
def true_iter():
while True:
while true_buffer:
yield true_buffer.popleft()
item = next(seq)
if pred(item):
yield item
else:
false_buffer.append(item)
def false_iter():
while True:
while false_buffer:
yield false_buffer.popleft()
item = next(seq)
if not pred(item):
yield item
else:
true_buffer.append(item)
return true_iter(), false_iter()

基本上,这个步骤遍历迭代器中的每个项目,检查谓词,如果正在使用相应的生成器,则生成谓词,或者将其放在另一个迭代器的缓冲区中。此外,每个生成器将首先从其缓冲区中提取项,然后再检查原始迭代器。请注意,每个分区都有自己的缓冲区,每次迭代另一个分区时缓冲区都会增大,因此这种实现可能不适合一个分区比另一个分区迭代次数多得多的用例。

示例用例:

from itertools import count
from random import random


odds, evens = partition(lambda n: n % 2, count())
for _ in range(500):
if random() < 0.5:
print(next(odds))
else:
print(next(evens))

对于 一个等价的问题的三个投票最多的答案建议使用 itertools.tee()(这里已经讨论过了)和两个更简单的方法。

collections.defaultdict方法是排序操作的优秀助手。

``

import collections
input_list = ['a','b','ana','beta','gamma']
filter_key = lambda x: len(x) == 1




## sorting code
cc = collections.defaultdict(list)
for item in input_list: cc[ filter_key(item) ].append( item )


print( cc )
This approach will also work for any number of categories generated by the `filter_key` function.