Python 如何根据谓词拆分序列?

Python 如何根据谓词拆分序列?,python,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',

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

这个假想的“拆分器”函数在实际运行中看起来是这样的:

>>> 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')]
我的问题是:

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')]
(上面的最后一个输出与前面显示的所需输出不同,满足谓词的元素的子序列是最后一个,而不是第一个,但这非常小,如果需要,很容易修复。)

我们可以避免对谓词的冗余调用(基本上是通过执行“内联记忆”),但我在这方面的最佳尝试非常复杂,与
拆分器(data,pred)
的简单性相去甚远:

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


分区就是其中之一。它使用内置的
filter()
函数来获取满足谓词的项,并获得与过滤器相反的效果,以确保在一个过程中迭代集合。这是标准/内置方法所能达到的最接近的结果

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)

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

这正是您想要的,是可以理解的,并且每个元素只对谓词求值一次:

def splitter(data, pred):
    yes, no = [], []
    for d in data:
        if pred(d):
            yes.append(d)
        else:
            no.append(d)
    return [yes, no]
如果您希望它更紧凑(出于某种原因):


如果您不关心效率,我认为
groupby
(或任何“将数据放入
n
bin”函数)有一些很好的对应关系

by_bins_iter = itertools.groupby(sorted(data, key=pred), key=pred)
by_bins = dict((k, tuple(v)) for k, v in by_bins_iter)
然后你可以通过

return by_bins.get(True, ()), by_bins.get(False, ())

上面使用
groupby
,对OP的一个实现和另一个评论者的实现进行了细微的修改:

groups = defaultdict(list, { k : list(ks) for k, ks in groupby(items, f) })

groups[True] == the matching items, or [] if none returned True
groups[False] == the non-matching items, or [] if none returned False
遗憾的是,正如您所指出的,
groupby
要求首先按谓词对项目进行排序,因此如果不能保证这一点,您需要:

groups = defaultdict(list, { k : list(ks) for k, ks in groupby(sorted(items, key=f), f) })
这是一个非常简单的表达式,但它是一个仅使用内置函数通过谓词对列表进行分区的表达式


我不认为在不使用
key
参数的情况下就可以使用
sorted
,因为
groupby
在点击key函数中的新值时会创建一个新组。因此,<>代码>排序只在项目由谓词提供自然排序的情况下工作。

< P>作为分区的一个更一般的解决方案,考虑分组。考虑下面的函数,由Culjule函数启发。

您给它一个要分组的项目集合,以及一个用于对它们进行分组的函数。代码如下:

def group_by(seq, f):

    groupings = {}

    for item in seq:
        res = f(item)
        if res in groupings:
            groupings[res].append(item)
        else:
            groupings[res] = [item]

    return groupings
对于OP的原始案例:

y = group_by(range(14), lambda i: int(i) % 3 == 2)
{False: [0, 1, 3, 4, 6, 7, 9, 10, 12, 13], True: [2, 5, 8, 11]}
按字符串长度对序列中的元素进行分组的更一般情况:

x = group_by(["x","xx","yy","zzz","z","7654321"], len)
{1: ['x', 'z'], 2: ['xx', 'yy'], 3: ['zzz'], 7: ['7654321']}

这可以扩展到许多情况,并且是函数式语言的核心功能。它与动态类型的python配合使用非常好,因为生成的映射中的键可以是任何类型。享受吧

内置模块
更多itertools
有一个名为
分区的函数,它完全满足topicstarter的要求

来自更多itertools导入分区
数字=[1,2,3,4,5,6,7]
谓词=lambda x:x%2==0
谓词\假,谓词\真=分区(谓词,数字)
打印(列表(谓词为假)、列表(谓词为真))

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

这将是实现这一点的明智方法。我想设置一个默认值
pred=bool
。我将结果作为元组返回,因为它应该始终正好是2个元素,但在其他方面,这是一个非常好的解决方案。您能帮助我们理解为什么不想编写函数吗?注意:这不是实现这一点的最佳解决方案,集合有效地迭代了两次。这是一种函数式方法,而不是命令式方法。可能更重要的是,它还对每个元素调用了两次谓词。更多的itertools在PyPI上,但它不在Python标准库中[wait…]哦,真的。事实上,我以为是的。谢谢你的指点!
def group_by(seq, f):

    groupings = {}

    for item in seq:
        res = f(item)
        if res in groupings:
            groupings[res].append(item)
        else:
            groupings[res] = [item]

    return groupings
y = group_by(range(14), lambda i: int(i) % 3 == 2)
{False: [0, 1, 3, 4, 6, 7, 9, 10, 12, 13], True: [2, 5, 8, 11]}
x = group_by(["x","xx","yy","zzz","z","7654321"], len)
{1: ['x', 'z'], 2: ['xx', 'yy'], 3: ['zzz'], 7: ['7654321']}