Как разбить последовательность в соответствии с предикатом?

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

Эта гипотетическая функция «сплиттера» будет выглядеть примерно так:

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

BTW, если вы не заботитесь о сохранении исходного заказа, 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')] 

Разделение является одним из тех рецептов itertools, которые делают именно это. Он использует 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) 

Я знаю, вы сказали, что не хотите писать свою собственную функцию, но я не могу себе представить, почему. Ваши решения включают в себя запись собственного кода, вы просто не модулируете их в функции.

Это делает именно то, что вы хотите, понятно и только оценивает предикат один раз для каждого элемента:

 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] 

Если вы не заботитесь об эффективности, я думаю, что 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, ())