2012-01-09 3 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')] 

(последний выход выше отличается от требуемого, как показано ранее в том, что подпоследовательность элементов, которые удовлетворяют предикату, наступает скорее, чем первая, но это очень незначительно и очень легко исправить при необходимости.)

Можно избежать избыточных вызовов предиката (в основном, inline 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 «S получает работу (поэтому параметр 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

Можете ли вы помочь нам понять, почему вы не хотите, чтобы написать функцию? –

+1

Возможный дубликат [Python: разбиение списка на основе условия?] (Http://stackoverflow.com/questions/949098/python-split-a-list-based-on-a-condition) – user

ответ

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

Примечание: это не будет оптимальным решением для этого является то, что коллекция эффективно повторяется дважды. Это касается скорее функционального подхода, чем императивного. –

+0

Возможно, что более важно, он также называет предикат дважды для каждого элемента. – user2357112

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

Это был бы разумный способ реализовать это. –

+1

Мне нравится устанавливать значение по умолчанию 'pred = bool'. – wap26

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,()) 
Смежные вопросы