74

假设我有一个列表和一个过滤功能。使用类似的东西

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

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

编辑:元素的顺序应该保持不变,我可能有多次相同的元素。

4

14 回答 14

61

尝试这个:

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.

于 2011-01-02T13:37:49.613 回答
28
>>> 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, ([], []))

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

于 2011-01-02T15:39:03.160 回答
10

TL;博士

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

是最简单和最快的。

对不同方法进行基准测试

建议的不同方法可以大致分为三类,

  1. 通过 直接操作列表lis.append,返回列表的 2 元组,
  2. lis.append由功能方法调解,返回一个 2 元组列表,
  3. 使用itertools精美文档中给出的规范配方,返回一个 2 元组,松散地说,生成器。

下面是三种技术的普通实现,首先是函数方法,然后itertools是直接列表操作的两种不同实现,另一种是使用False0,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报告了该问题

废话。您已经计算了在filterfalseand中构造生成器所花费的时间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 日今天接受和投票最多 - 但我当然对我的这个答案寄予厚望!

于 2017-09-14T10:48:36.613 回答
7

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]}
于 2012-04-19T20:18:21.097 回答
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/上找到

于 2017-08-29T20:26:58.767 回答
3

我刚好有这个要求。我不喜欢 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])
于 2012-08-15T11:35:52.043 回答
3

如果您的列表中没有重复元素,您绝对可以使用 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() 结果,您就可以推断出与您的过滤条件无关的元素。

于 2011-01-02T13:48:08.857 回答
2
from itertools import ifilterfalse

def filter2(predicate, iterable):
    return filter(predicate, iterable), list(ifilterfalse(predicate, iterable))
于 2011-01-02T13:54:30.947 回答
2

现有的答案要么将一个可迭代对象划分为两个列表,要么将其划分为两个生成器。这是一个有效地将可迭代对象划分为两个生成器的实现,即对于可迭代对象中的每个元素最多调用一次谓词函数。您可能想要使用此版本的一个实例是,如果您需要对一个非常大(甚至无限)的可迭代对象进行分区,并且计算谓词的计算成本很高。

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))
于 2019-12-23T16:32:03.550 回答
1

每个人似乎都认为他们的解决方案是最好的,所以我决定使用 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 倍的优势击败了所有人!道德是,不要继续重新发明轮子。

于 2013-08-08T13:05:04.707 回答
0

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

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 ) )
于 2012-06-06T14:37:56.760 回答
0

建议使用的等效问题的三个最高投票答案itertools.tee()(如这里已经介绍过的)和两个甚至更简单的方法。

于 2021-01-28T12:42:35.557 回答
0

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

    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]
于 2018-10-15T18:33:33.260 回答
0

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.
于 2021-04-07T08:59:11.033 回答