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

F'x*_*F'x 54 python filter data-partitioning

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

>>> filter(lambda x: x > 10, [1,4,12,7,42])
[12, 42]
Run Code Online (Sandbox Code Playgroud)

我可以得到符合标准的元素.是否有一个我可以使用的函数可以输出两个列表,一个元素匹配,剩下的元素之一?我可以filter()两次调用该函数,但这有点难看:)

编辑:元素的顺序应该是守恒的,我可能多次使用相同的元素.

Mar*_*ers 46

试试这个:

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

用法:

>>> trues, falses = partition(lambda x: x > 10, [1,4,12,7,42])
>>> trues
[12, 42]
>>> falses
[1, 4, 7]
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

配方来自Python 3.x文档.在Python 2.x filterfalse被调用ifilterfalse.

  • 该操作通常称为"分区".它用于快速排序等. (11认同)
  • @MizardX:+1用于名称建议.我用谷歌搜索它,实际上在itertools食谱中实现了`partition`:http://docs.python.org/dev/library/itertools.html#itertools-recipes (3认同)
  • 除非您不只是想要两个列表,否则`itertools`配方可能不值得:它对谓词的调用次数是原来的两倍,因此花费的时间几乎是原来的两倍。而且代码不清晰。 (2认同)
  • `(trues if pred(item) else falses).append(item)` /sf/ask/3154321481/#comment77096817_45061735 (2认同)

Mar*_*riy 21

>>> 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])
Run Code Online (Sandbox Code Playgroud)

上面的代码有点丑陋但速度更快的版本:

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,  ([], []))
Run Code Online (Sandbox Code Playgroud)

这是第二次编辑,但我认为这很重要:

 def partition(l, p):
     return reduce(lambda x, y: x[not p(y)].append(y) or x, l, ([], []))
Run Code Online (Sandbox Code Playgroud)

第二个和第三个与迭代的上层一样快,但代码较少.

  • 来自功能部门,我发现这个解决方案最优雅. (6认同)
  • @alcalde 我认为如果您熟悉函数式语言的累加器/折叠模式,可能会更容易立即看到这一点。例如,第一个版本的工作原理就像我在 Ocaml 中编写 fold_left 一样。`p(y)` 检查条件,然后将当前元素附加到第一个或第二个列表。如果你不喜欢 `x[0]+[y]` 语法,那么就使用 `x[0].append(y)` 作为第二个解决方案。我发现最后一个最不理想,因为它取决于被解释为整数索引的布尔值(`not p(y)`)。 (4认同)
  • @josch您是否真的觉得*可读*?甚至还不清楚该代码的任何版本做什么。 (2认同)

vis*_*ell 7

你可以看看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
Run Code Online (Sandbox Code Playgroud)

在我看来,这是这里提出的最优雅的解决方案。

这部分没有记录,只能在https://docs.djangoproject.com/en/dev/_modules/django/utils/functioning/上找到源代码


小智 6

我认为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]}
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,这调用了两次 key 。如果 key 是一个潜在昂贵的操作(例如测试远程 URL 是否可访问),则此解决方案将不起作用。 (2认同)

gbo*_*ffi 5

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
Run Code Online (Sandbox Code Playgroud)

是最简单,最快的。

对不同方法进行基准测试

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

  1. 通过进行简单的列表操作lis.append,返回2元组列表,
  2. lis.append 以功能性方法为中介,返回2元组的列表,
  3. 使用itertools优质文档中提供的规范配方,返回一个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
   ...: 
Run Code Online (Sandbox Code Playgroud)

我们需要一个谓词来应用到我们的列表以及要对其进行操作的列表(再次,粗略地说)。

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

In [6]: five, ten = range(50000), range(100000)
Run Code Online (Sandbox Code Playgroud)

为了克服测试itertools方法的问题,joeln于13年10月31日在6:17报告

废话。您已经计算了在filterfalse和中构造生成器所花费的时间filter,但尚未遍历输入或调用pred一次!itertools配方的优点 在于它不会实现任何列表,也不会在输入中超出必要的范围。它的通话pred频率是Byers等人的两倍,几乎是其两倍。

我想到了一个空循环,它仅实例化了由不同分区函数返回的两个可迭代对象中的所有元素对。

首先,我们使用两个固定列表来了解隐含的过载(使用非常方便的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)
Run Code Online (Sandbox Code Playgroud)

接下来,我们使用不同的实现,一个接一个

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]:
Run Code Online (Sandbox Code Playgroud)

评论

最简单的方法也是最快的一种。

使用这个x[p(n)]技巧是毫无用处的,因为在每一步中您都必须为数据结构建立索引,从而给您带来一点点损失–但是,很高兴知道您是否想在python化过程中说服幸存者。

功能的方法,即操作性地等同于替代append实施方式中,是〜50%比较慢,这可能是由于这样的事实,我们有一个额外的(W / R到谓词求值)函数调用为每个列表元素。

itertools方法具有(常规)优势?没有实例化潜在的大列表,并且?如果您脱离使用者循环,则不会完全处理输入列表,但是当我们使用它时,它会变慢,因为需要在谓词的两端应用谓词。tee

在旁边

我爱上object.mutate() or objectMarii他们的回答中暴露出来的惯用语,显示了解决问题的实用方法-恐怕迟早会滥用它。

脚注

[1]截至今天,2017年9月14日,该投票被接受并获得最多投票-但是,对于我的这个回答,我当然寄予了最高的希望!