如何根据谓词拆分序列?

kjo*_*kjo 23 python

我经常遇到需要将一个序列拆分为满足和不满足给定谓词的元素的两个子序列(保留原始的相对排序).

这个假设的"分离器"功能看起来像这样:

>>> data = map(str, range(14))
>>> pred = lambda i: int(i) % 3 == 2
>>> splitter(data, pred)
[('2', '5', '8', '11'), ('0', '1', '3', '4', '6', '7', '9', '10', '12', '13')]
Run Code Online (Sandbox Code Playgroud)

我的问题是:

Python已经有标准/内置方式来做到这一点吗?

这个功能当然不难编码(参见下面的附录),但由于多种原因,我更喜欢使用标准/内置方法而不是自动方法.

谢谢!



附录:

到目前为止,我发现用于在Python中处理此任务的最佳标准函数是itertools.groupby.但是,要将它用于此特定任务,必须为每个列表成员调用谓词函数两次,我觉得这很烦人:

>>> import itertools as it
>>> [tuple(v[1]) for v in it.groupby(sorted(data, key=pred), key=pred)]
[('0', '1', '3', '4', '6', '7', '9', '10', '12', '13'), ('2', '5', '8', '11')]
Run Code Online (Sandbox Code Playgroud)

(上面的最后一个输出与前面所示的那个不同之处在于,满足谓词的元素的子序列是最后一个而不是第一个,但这非常小,如果需要很容易修复.)

人们可以避免对谓词的冗余调用(基本上,通过"内联memoization"),但我对此的最好的抨击得到了相当精细,与以下简单性相去甚远splitter(data, pred):

>>> first = lambda t: t[0]
>>> [zip(*i[1])[1] for i in it.groupby(sorted(((pred(x), x) for x in data),
... key=first), key=first)]
[('0', '1', '3', '4', '6', '7', '9', '10', '12', '13'), ('2', '5', '8', '11')]
Run Code Online (Sandbox Code Playgroud)

顺便说一句,如果您不关心保留原始排序,则sorted默认排序顺序可以完成工作(因此key参数可能会从sorted调用中省略):

>>> [zip(*i[1])[1] for i in it.groupby(sorted(((pred(x), x) for x in data)),
... key=first)]
[('0', '1', '3', '4', '6', '7', '9', '10', '12', '13'), ('2', '5', '8', '11')]
Run Code Online (Sandbox Code Playgroud)

Ned*_*der 31

我知道你说你不想写自己的功能,但我无法想象为什么.您的解决方案涉及编写自己的代码,您只是没有将它们模块化为函数.

这完全符合您的要求,可以理解,并且每个元素只评估一次谓词:

def splitter(data, pred):
    yes, no = [], []
    for d in data:
        if pred(d):
            yes.append(d)
        else:
            no.append(d)
    return [yes, no]
Run Code Online (Sandbox Code Playgroud)

如果你想要它更紧凑(由于某种原因):

def splitter(data, pred):
    yes, no = [], []
    for d in data:
        (yes if pred(d) else no).append(d)
    return [yes, no]
Run Code Online (Sandbox Code Playgroud)


Jef*_*ado 17

分区是那些只做到这一点的itertools配方之一.它用于tee()确保它在一次传递中迭代集合,尽管有多个迭代器,内置filter()函数用于获取满足谓词的项目以及filterfalse()获得过滤器的相反效果.这就像你要获得标准/内置方法一样接近.

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)

  • 注意:这不是执行此操作的最佳解决方案,该集合有效地迭代两次.这是一种功能性方法,而不是命令式方法. (9认同)
  • 可能更重要的是,它还对每个元素调用了两次谓词。 (2认同)

Pan*_*877 10

在内置模块中more_itertools有一个名为 的函数partition,它完全符合 topicstarter 的要求。

from more_itertools import partition

numbers = [1, 2, 3, 4, 5, 6, 7]
predicate = lambda x: x % 2 == 0

predicate_false, predicate_true = partition(predicate, numbers)

print(list(predicate_false), list(predicate_true))
Run Code Online (Sandbox Code Playgroud)

结果是[1, 3, 5, 7] [2, 4, 6]

  • more_itertools 在 PyPI 上,但不在 Python 标准库中 (2认同)