2012-01-09 35 views
14

我经常遇到需要将一个序列分割成满足和不满足给定谓词(保留原始相对排序)的元素的两个子序列。如何根据谓词分割一个序列?

这个假设的“分流”功能会看在行动中是这样的:

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

(最后输出上述从先前在该示出的期望的一个不同满足谓词元素的子说到最后,而不是第一个,但这是非常轻微的,而且很容易,如果需要修理。)

人能避免谓词冗余呼叫(这样做,基本上是一个“内联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')] 

顺便说一句,如果你不关心保留原来的排序,的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')] 
+0

可以帮助我们理解为什么你不想写一个函数? – 2012-01-09 19:31:42

+1

可能重复的[Python:拆分基于条件的列表?](http://stackoverflow.com/questions/949098/python-split-a-list-based-on-a-condition) – user 2014-09-21 03:03:47

回答

12

分区就是那样的itertools recipes之一。它使用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) 
+7

注意:这wouldn这不是做到这一点的最佳解决方案,这个集合有效地迭代了两次。这是一个功能性的方法,而不是一个必要的方法。 – 2012-01-10 03:19:42

+0

可能更重要的是,它也会在每个元素上两次调用谓词。 – user2357112 2017-09-14 17:04:38

23

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

这不正是你想要的东西,是可以理解的,只有每个元素一次计算谓词:

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

如果你希望它是更紧凑(出于某种原因):

def splitter(data, pred): 
    yes, no = [], [] 
    for d in data: 
     (yes if pred(d) else no).append(d) 
    return [yes, no] 
+0

这将是实现这一点的理智方式。 – 2012-01-09 19:29:39

+1

我喜欢设置一个默认值'pred = bool'。 – wap26 2017-02-11 09:12:06

1

如果你不在乎效率,我认为groupby(或任何“把数据放入n箱”功能)有一些很好的对应关系,

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,())