假设我有一个列表和一个过滤功能。使用类似的东西
>>> filter(lambda x: x > 10, [1,4,12,7,42])
[12, 42]
我可以得到符合条件的元素。有没有我可以使用的函数来输出两个列表,一个元素匹配,一个剩余元素?我可以调用该filter()
函数两次,但这有点难看:)
编辑:元素的顺序应该保持不变,我可能有多次相同的元素。
假设我有一个列表和一个过滤功能。使用类似的东西
>>> filter(lambda x: x > 10, [1,4,12,7,42])
[12, 42]
我可以得到符合条件的元素。有没有我可以使用的函数来输出两个列表,一个元素匹配,一个剩余元素?我可以调用该filter()
函数两次,但这有点难看:)
编辑:元素的顺序应该保持不变,我可能有多次相同的元素。
尝试这个:
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.xfilterfalse
中称为ifilterfalse
.
>>> 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, ([], []))
第二个和第三个和上面的迭代一样快,但代码更少。
Mark Byers接受的、投票最多的答案[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
精美文档中给出的规范配方,返回一个 2 元组,松散地说,生成器。下面是三种技术的普通实现,首先是函数方法,然后itertools
是直接列表操作的两种不同实现,另一种是使用False
0,True
这是一个技巧。
请注意,这是 Python3 - 因此reduce
来自functools
- 并且 OP 请求一个元组,(positives, negatives)
但我的实现都返回(negatives, positives)
......</p>
$ 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于 2013 年 10 月 31 日 6:17报告了该问题
废话。您已经计算了在
filterfalse
and中构造生成器所花费的时间filter
,但是您没有遍历输入或调用pred
一次!配方的优点itertools
是它不会具体化任何列表,或者在输入中比必要的更远。它的调用pred
频率是 Byers 等人的两倍,所需时间几乎是 Byers 等人的两倍。
我想到了一个 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)]
技巧,嗯,没用,因为在每一步你都必须索引一个数据结构,这会给你带来轻微的惩罚——然而,如果你想说服一个正在衰退的文化中的幸存者进行 Python 化,那就太好了。
功能性方法在操作上等同于替代append
实现,速度慢了约 50%,这可能是因为我们对每个列表元素都有一个额外的(w/r 到谓词评估)函数调用。
该itertools
方法具有(惯用的)优点:❶ 没有实例化潜在的大列表,❷ 如果你跳出消费者循环,输入列表不会被完全处理,但是当我们使用它时它会更慢,因为需要应用谓词在两端tee
我已经爱上了Marii
在他们的回答object.mutate() or object
中展示了解决问题的实用方法的成语——恐怕迟早我会滥用它。
[1] 2017 年 9 月 14 日今天接受和投票最多 - 但我当然对我的这个答案寄予厚望!
I think groupby might be more relevant here:
http://docs.python.org/library/itertools.html#itertools.groupby
For example, splitting a list into odd and even numbers (or could be an arbitrary number of groups):
>>> 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]}
你可以看看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/上找到
我刚好有这个要求。我不喜欢 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])
如果您的列表中没有重复元素,您绝对可以使用 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))
现有的答案要么将一个可迭代对象划分为两个列表,要么将其划分为两个生成器。这是一个有效地将可迭代对象划分为两个生成器的实现,即对于可迭代对象中的每个元素最多调用一次谓词函数。您可能想要使用此版本的一个实例是,如果您需要对一个非常大(甚至无限)的可迭代对象进行分区,并且计算谓词的计算成本很高。
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))
每个人似乎都认为他们的解决方案是最好的,所以我决定使用 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 倍的优势击败了所有人!道德是,不要继续重新发明轮子。
已经有很多好的答案了。我喜欢用这个:
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.tee()
(如这里已经介绍过的)和两个甚至更简单的方法。
附加到目标列表的简洁代码
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]
该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.