2012-01-09 41 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,())