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']}