Python: разделить список на основе условия?
Как лучше, с эстетической точки зрения и с точки зрения производительности, разделить список элементов на несколько списков на основе условного обозначения? Эквивалент:
good = [x for x in mylist if x in goodvals]
bad = [x for x in mylist if x not in goodvals]
Есть ли более элегантный способ сделать это?
Обновление: вот фактический вариант использования, чтобы лучше объяснить, что я пытаюсь сделать:
# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims = [f for f in files if f[2].lower() not in IMAGE_TYPES]
41 ответ
good = [x for x in mylist if x in goodvals] bad = [x for x in mylist if x not in goodvals]
Есть ли более элегантный способ сделать это?
Этот код отлично читается и предельно понятен!
# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims = [f for f in files if f[2].lower() not in IMAGE_TYPES]
Опять же, это хорошо!
Могут быть небольшие улучшения производительности при использовании наборов, но это тривиальное различие, и я считаю, что понимание списка гораздо легче читать, и вам не нужно беспокоиться о том, что порядок будет испорчен, дубликаты будут удалены, и так далее.
На самом деле, я могу сделать еще один шаг назад и просто использовать простой цикл for:
images, anims = [], []
for f in files:
if f.lower() in IMAGE_TYPES:
images.append(f)
else:
anims.append(f)
Список-понимание или использование set()
Это нормально, пока вам не понадобится добавить какую-либо другую проверку или другую логику - скажем, вы хотите удалить все 0-байтовые jpeg, вы просто добавляете что-то вроде..
if f[1] == 0:
continue
good, bad = [], []
for x in mylist:
(bad, good)[x in goodvals].append(x)
Вот ленивый подход итератора:
from itertools import tee
def split_on_condition(seq, condition):
l1, l2 = tee((condition(item), item) for item in seq)
return (i for p, i in l1 if p), (i for p, i in l2 if not p)
Он оценивает условие один раз для каждого элемента и возвращает два генератора, сначала получая значения из последовательности, где условие истинно, а другое - ложно.
Поскольку это лениво, вы можете использовать его на любом итераторе, даже бесконечном:
from itertools import count, islice
def is_prime(n):
return n > 1 and all(n % i for i in xrange(2, n))
primes, not_primes = split_on_condition(count(), is_prime)
print("First 10 primes", list(islice(primes, 10)))
print("First 10 non-primes", list(islice(not_primes, 10)))
Обычно подход с возвратом неленивых списков лучше:
def split_on_condition(seq, condition):
a, b = [], []
for item in seq:
(a if condition(item) else b).append(item)
return a, b
Редактировать: для вашего более конкретного использования разделения элементов в разные списки по некоторому ключу, вот общая функция, которая делает это:
DROP_VALUE = lambda _:_
def split_by_key(seq, resultmapping, keyfunc, default=DROP_VALUE):
"""Split a sequence into lists based on a key function.
seq - input sequence
resultmapping - a dictionary that maps from target lists to keys that go to that list
keyfunc - function to calculate the key of an input value
default - the target where items that don't have a corresponding key go, by default they are dropped
"""
result_lists = dict((key, []) for key in resultmapping)
appenders = dict((key, result_lists[target].append) for target, keys in resultmapping.items() for key in keys)
if default is not DROP_VALUE:
result_lists.setdefault(default, [])
default_action = result_lists[default].append
else:
default_action = DROP_VALUE
for item in seq:
appenders.get(keyfunc(item), default_action)(item)
return result_lists
Использование:
def file_extension(f):
return f[2].lower()
split_files = split_by_key(files, {'images': IMAGE_TYPES}, keyfunc=file_extension, default='anims')
print split_files['images']
print split_files['anims']
Проблема всех предложенных решений заключается в том, что он будет сканировать и применять функцию фильтрации дважды. Я бы сделал простую маленькую функцию, такую как эта:
def SplitIntoTwoLists(l, f):
a = []
b = []
for i in l:
if f(i):
a.append(i)
else:
b.append(i)
return (a,b)
Таким образом, вы ничего не обрабатываете дважды, а также не повторяете код.
Мой взгляд на это. Я предлагаю ленивый, однопроходный, partition
функция, которая сохраняет относительный порядок в выходных подпоследовательностях.
1. Требования
Я предполагаю, что требования:
- поддерживать относительный порядок элементов (следовательно, нет наборов и словарей)
- оценивать условие только один раз для каждого элемента (следовательно, не используя (
i
)filter
или жеgroupby
) - учитывает ленивое потребление любой последовательности (если мы можем позволить себе предварительно вычислить их, то наивная реализация, вероятно, также будет приемлемой)
2. split
библиотека
мой partition
Функция (представленная ниже) и другие подобные функции превратили ее в небольшую библиотеку:
Обычно устанавливается через PyPI:
pip install --user split
Чтобы разделить список по условию, используйте partition
функция:
>>> from split import partition
>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi') ]
>>> image_types = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> images, other = partition(lambda f: f[-1] in image_types, files)
>>> list(images)
[('file1.jpg', 33L, '.jpg')]
>>> list(other)
[('file2.avi', 999L, '.avi')]
3. partition
функция объяснила
Внутренне нам нужно построить две подпоследовательности одновременно, поэтому использование только одной выходной последовательности приведет к вычислению и другой. И нам нужно сохранять состояние между пользовательскими запросами (хранить обработанные, но еще не запрошенные элементы). Чтобы сохранить состояние, я использую две двусторонние очереди (deques
):
from collections import deque
SplitSeq
класс заботится о ведении домашнего хозяйства:
class SplitSeq:
def __init__(self, condition, sequence):
self.cond = condition
self.goods = deque([])
self.bads = deque([])
self.seq = iter(sequence)
Волшебство происходит в его .getNext()
метод. Это почти как .next()
итераторов, но позволяет указать, какой тип элемента мы хотим на этот раз. За сценой он не отбрасывает отклоненные элементы, а помещает их в одну из двух очередей:
def getNext(self, getGood=True):
if getGood:
these, those, cond = self.goods, self.bads, self.cond
else:
these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
if these:
return these.popleft()
else:
while 1: # exit on StopIteration
n = self.seq.next()
if cond(n):
return n
else:
those.append(n)
Конечный пользователь должен использовать partition
функция. Требуется функция условия и последовательность (так же, как map
или же filter
) и возвращает два генератора. Первый генератор создает подпоследовательность элементов, для которых выполняется условие, второй - дополнительную подпоследовательность. Итераторы и генераторы допускают ленивое расщепление даже длинных или бесконечных последовательностей.
def partition(condition, sequence):
cond = condition if condition else bool # evaluate as bool if condition == None
ss = SplitSeq(cond, sequence)
def goods():
while 1:
yield ss.getNext(getGood=True)
def bads():
while 1:
yield ss.getNext(getGood=False)
return goods(), bads()
Я выбрал тестовую функцию в качестве первого аргумента для облегчения частичного применения в будущем (аналогично тому, как map
а также filter
иметь тестовую функцию в качестве первого аргумента).
Мне в основном нравится подход Андерса, так как он очень общий. Вот версия, которая ставит классификатор на первое место (для соответствия синтаксису фильтра) и использует defaultdict (предполагается, импортированный).
def categorize(func, seq):
"""Return mapping from categories to lists
of categorized items.
"""
d = defaultdict(list)
for item in seq:
d[func(item)].append(item)
return d
Это самый быстрый способ.
Оно использует if else
(как ответ dbr), но сначала создает набор. Набор уменьшает число операций с O(m * n) до O(log m) + O(n), что приводит к увеличению скорости на 45%+.
good_list_set = set(good_list) # 45% faster than a tuple.
good, bad = [], []
for item in my_origin_list:
if item in good_list_set:
good.append(item)
else:
bad.append(item)
Немного короче:
good_list_set = set(good_list) # 45% faster than a tuple.
good, bad = [], []
for item in my_origin_list:
out = good if item in good_list_set else bad
out.append(item)
Результаты тестов:
filter_BJHomer 80/s -- -3265% -5312% -5900% -6262% -7273% -7363% -8051% -8162% -8244%
zip_Funky 118/s 4848% -- -3040% -3913% -4450% -5951% -6085% -7106% -7271% -7393%
two_lst_tuple_JohnLaRoy 170/s 11332% 4367% -- -1254% -2026% -4182% -4375% -5842% -6079% -6254%
if_else_DBR 195/s 14392% 6428% 1434% -- -882% -3348% -3568% -5246% -5516% -5717%
two_lst_compr_Parand 213/s 16750% 8016% 2540% 967% -- -2705% -2946% -4786% -5083% -5303%
if_else_1_line_DanSalmo 292/s 26668% 14696% 7189% 5033% 3707% -- -331% -2853% -3260% -3562%
tuple_if_else 302/s 27923% 15542% 7778% 5548% 4177% 343% -- -2609% -3029% -3341%
set_1_line 409/s 41308% 24556% 14053% 11035% 9181% 3993% 3529% -- -569% -991%
set_shorter 434/s 44401% 26640% 15503% 12303% 10337% 4836% 4345% 603% -- -448%
set_if_else 454/s 46952% 28358% 16699% 13349% 11290% 5532% 5018% 1100% 469% --
Полный код теста для Python 3.7 (модифицированный из FunkySayu):
good_list = ['.jpg','.jpeg','.gif','.bmp','.png']
import random
import string
my_origin_list = []
for i in range(10000):
fname = ''.join(random.choice(string.ascii_lowercase) for i in range(random.randrange(10)))
if random.getrandbits(1):
fext = random.choice(list(good_list))
else:
fext = "." + ''.join(random.choice(string.ascii_lowercase) for i in range(3))
my_origin_list.append((fname + fext, random.randrange(1000), fext))
# Parand
def two_lst_compr_Parand(*_):
return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]
# dbr
def if_else_DBR(*_):
a, b = list(), list()
for e in my_origin_list:
if e[2] in good_list:
a.append(e)
else:
b.append(e)
return a, b
# John La Rooy
def two_lst_tuple_JohnLaRoy(*_):
a, b = list(), list()
for e in my_origin_list:
(b, a)[e[2] in good_list].append(e)
return a, b
# # Ants Aasma
# def f4():
# l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
# return [i for p, i in l1 if p], [i for p, i in l2 if not p]
# My personal way to do
def zip_Funky(*_):
a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
return list(filter(None, a)), list(filter(None, b))
# BJ Homer
def filter_BJHomer(*_):
return list(filter(lambda e: e[2] in good_list, my_origin_list)), list(filter(lambda e: not e[2] in good_list, my_origin_list))
# ChaimG's answer; as a list.
def if_else_1_line_DanSalmo(*_):
good, bad = [], []
for e in my_origin_list:
_ = good.append(e) if e[2] in good_list else bad.append(e)
return good, bad
# ChaimG's answer; as a set.
def set_1_line(*_):
good_list_set = set(good_list)
good, bad = [], []
for e in my_origin_list:
_ = good.append(e) if e[2] in good_list_set else bad.append(e)
return good, bad
# ChaimG set and if else list.
def set_shorter(*_):
good_list_set = set(good_list)
good, bad = [], []
for e in my_origin_list:
out = good if e[2] in good_list_set else bad
out.append(e)
return good, bad
# ChaimG's best answer; if else as a set.
def set_if_else(*_):
good_list_set = set(good_list)
good, bad = [], []
for e in my_origin_list:
if e[2] in good_list_set:
good.append(e)
else:
bad.append(e)
return good, bad
# ChaimG's best answer; if else as a set.
def tuple_if_else(*_):
good_list_tuple = tuple(good_list)
good, bad = [], []
for e in my_origin_list:
if e[2] in good_list_tuple:
good.append(e)
else:
bad.append(e)
return good, bad
def cmpthese(n=0, functions=None):
results = {}
for func_name in functions:
args = ['%s(range(256))' % func_name, 'from __main__ import %s' % func_name]
t = Timer(*args)
results[func_name] = 1 / (t.timeit(number=n) / n) # passes/sec
functions_sorted = sorted(functions, key=results.__getitem__)
for f in functions_sorted:
diff = []
for func in functions_sorted:
if func == f:
diff.append("--")
else:
diff.append(f"{results[f]/results[func]*100 - 100:5.0%}")
diffs = " ".join(f'{x:>8s}' for x in diff)
print(f"{f:27s} \t{results[f]:,.0f}/s {diffs}")
if __name__=='__main__':
from timeit import Timer
cmpthese(1000, 'two_lst_compr_Parand if_else_DBR two_lst_tuple_JohnLaRoy zip_Funky filter_BJHomer if_else_1_line_DanSalmo set_1_line set_if_else tuple_if_else set_shorter'.split(" "))
Сначала (предварительное редактирование): Используйте наборы:
mylist = [1,2,3,4,5,6,7]
goodvals = [1,3,7,8,9]
myset = set(mylist)
goodset = set(goodvals)
print list(myset.intersection(goodset)) # [1, 3, 7]
print list(myset.difference(goodset)) # [2, 4, 5, 6]
Это хорошо как для читабельности (IMHO), так и для производительности.
Второй ход (после OP-редактирования):
Создайте список хороших расширений в виде набора:
IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])
и это повысит производительность. В противном случае, то, что у вас есть, выглядит хорошо для меня.
itertools.groupby почти делает то, что вы хотите, за исключением того, что требуется сортировка элементов, чтобы гарантировать, что вы получите один непрерывный диапазон, поэтому вам нужно сначала отсортировать по ключу (в противном случае вы получите несколько групп с чередованием для каждого типа). например.
def is_good(f):
return f[2].lower() in IMAGE_TYPES
files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file3.gif', 123L, '.gif')]
for key, group in itertools.groupby(sorted(files, key=is_good), key=is_good):
print key, list(group)
дает:
False [('file2.avi', 999L, '.avi')]
True [('file1.jpg', 33L, '.jpg'), ('file3.gif', 123L, '.gif')]
Как и в других решениях, ключевая функция может быть определена для разделения на любое количество групп, которые вы хотите.
good.append(x) if x in goodvals else bad.append(x)
Этот элегантный и лаконичный ответ @dansalmo был похоронен в комментариях, поэтому я просто репостю его здесь как ответ, чтобы он мог получить заслуженное внимание, особенно для новых читателей.
Полный пример:
good, bad = [], []
for x in my_list:
good.append(x) if x in goodvals else bad.append(x)
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]
append возвращает None, поэтому он работает.
Лично мне нравится версия, которую вы цитировали, если у вас уже есть список goodvals
торчать Если нет, то что-то вроде:
good = filter(lambda x: is_good(x), mylist)
bad = filter(lambda x: not is_good(x), mylist)
Конечно, это действительно очень похоже на использование понимания списка, как вы делали изначально, но с функцией вместо поиска:
good = [x for x in mylist if is_good(x)]
bad = [x for x in mylist if not is_good(x)]
В целом, я считаю, что эстетика списочного понимания очень приятна. Конечно, если вам не нужно сохранять порядок и вам не нужны дубликаты, используйте intersection
а также difference
методы на множествах тоже будут хорошо работать.
Иногда кажется, что понимание списка - не лучшая вещь для использования!
Я сделал небольшой тест, основанный на ответе людей на эту тему, протестированном по случайному списку. Вот генерация списка (возможно, есть лучший способ сделать это, но это не главное):
good_list = ('.jpg','.jpeg','.gif','.bmp','.png')
import random
import string
my_origin_list = []
for i in xrange(10000):
fname = ''.join(random.choice(string.lowercase) for i in range(random.randrange(10)))
if random.getrandbits(1):
fext = random.choice(good_list)
else:
fext = "." + ''.join(random.choice(string.lowercase) for i in range(3))
my_origin_list.append((fname + fext, random.randrange(1000), fext))
И здесь мы идем
# Parand
def f1():
return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]
# dbr
def f2():
a, b = list(), list()
for e in my_origin_list:
if e[2] in good_list:
a.append(e)
else:
b.append(e)
return a, b
# John La Rooy
def f3():
a, b = list(), list()
for e in my_origin_list:
(b, a)[e[2] in good_list].append(e)
return a, b
# Ants Aasma
def f4():
l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
return [i for p, i in l1 if p], [i for p, i in l2 if not p]
# My personal way to do
def f5():
a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
return list(filter(None, a)), list(filter(None, b))
# BJ Homer
def f6():
return filter(lambda e: e[2] in good_list, my_origin_list), filter(lambda e: not e[2] in good_list, my_origin_list)
Используя функцию cmpthese, лучший результат - ответ dbr:
f1 204/s -- -5% -14% -15% -20% -26%
f6 215/s 6% -- -9% -11% -16% -22%
f3 237/s 16% 10% -- -2% -7% -14%
f4 240/s 18% 12% 2% -- -6% -13%
f5 255/s 25% 18% 8% 6% -- -8%
f2 277/s 36% 29% 17% 15% 9% --
Если вы хотите сделать это в стиле FP:
good, bad = [ sum(x, []) for x in zip(*(([y], []) if y in goodvals else ([], [y])
for y in mylist)) ]
Не самое читаемое решение, но, по крайней мере, перебирает mylist только один раз.
Я думаю, что обобщение разбиения итерируемого на основе N условий удобно
from collections import OrderedDict
def partition(iterable,*conditions):
'''Returns a list with the elements that satisfy each of condition.
Conditions are assumed to be exclusive'''
d= OrderedDict((i,list())for i in range(len(conditions)))
for e in iterable:
for i,condition in enumerate(conditions):
if condition(e):
d[i].append(e)
break
return d.values()
Например:
ints,floats,other = partition([2, 3.14, 1, 1.69, [], None],
lambda x: isinstance(x, int),
lambda x: isinstance(x, float),
lambda x: True)
print " ints: {}\n floats:{}\n other:{}".format(ints,floats,other)
ints: [2, 1]
floats:[3.14, 1.69]
other:[[], None]
Если элемент может удовлетворять нескольким условиям, удалите разрыв.
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)
Проверьте это
Если вы не возражаете против использования внешней библиотеки, я знаю, что наивно реализовать эту операцию:
>>> files = [ ('file1.jpg', 33, '.jpg'), ('file2.avi', 999, '.avi')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
iteration_utilities.partition
:>>> from iteration_utilities import partition >>> notimages, images = partition(files, lambda x: x[2].lower() in IMAGE_TYPES) >>> notimages [('file2.avi', 999, '.avi')] >>> images [('file1.jpg', 33, '.jpg')]
more_itertools.partition
>>> from more_itertools import partition >>> notimages, images = partition(lambda x: x[2].lower() in IMAGE_TYPES, files) >>> list(notimages) # returns a generator so you need to explicitly convert to list. [('file2.avi', 999, '.avi')] >>> list(images) [('file1.jpg', 33, '.jpg')]
Например, разделение списка на четные и нечетные
arr = range(20)
even, odd = reduce(lambda res, next: res[next % 2].append(next) or res, arr, ([], []))
Или вообще:
def split(predicate, iterable):
return reduce(lambda res, e: res[predicate(e)].append(e) or res, iterable, ([], []))
Преимущества:
- Кратчайший возможный путь
- Предикат применяется только один раз для каждого элемента
Недостатки
- Требует знания парадигмы функционального программирования
Вдохновленный отличным (но кратким!) Ответом@ gnibbler, мы можем применить этот подход для сопоставления с несколькими разделами:
from collections import defaultdict
def splitter(l, mapper):
"""Split an iterable into multiple partitions generated by a callable mapper."""
results = defaultdict(list)
for x in l:
results[mapper(x)] += [x]
return results
затем splitter
затем можно использовать следующим образом:
>>> l = [1, 2, 3, 4, 2, 3, 4, 5, 6, 4, 3, 2, 3]
>>> split = splitter(l, lambda x: x % 2 == 0) # partition l into odds and evens
>>> split.items()
>>> [(False, [1, 3, 3, 5, 3, 3]), (True, [2, 4, 2, 4, 6, 4, 2])]
Это работает для более чем двух разделов с более сложным отображением (и на итераторах тоже):
>>> import math
>>> l = xrange(1, 23)
>>> split = splitter(l, lambda x: int(math.log10(x) * 5))
>>> split.items()
[(0, [1]),
(1, [2]),
(2, [3]),
(3, [4, 5, 6]),
(4, [7, 8, 9]),
(5, [10, 11, 12, 13, 14, 15]),
(6, [16, 17, 18, 19, 20, 21, 22])]
Или используя словарь для сопоставления:
>>> map = {'A': 1, 'X': 2, 'B': 3, 'Y': 1, 'C': 2, 'Z': 3}
>>> l = ['A', 'B', 'C', 'C', 'X', 'Y', 'Z', 'A', 'Z']
>>> split = splitter(l, map.get)
>>> split.items()
(1, ['A', 'Y', 'A']), (2, ['C', 'C', 'X']), (3, ['B', 'Z', 'Z'])]
Еще одно решение этой проблемы. Мне нужно было решение как можно быстрее. Это означает только одну итерацию по списку и предпочтительно O(1) для добавления данных в один из результирующих списков. Это очень похоже на решение, предоставляемое састанином, за исключением гораздо более короткого:
from collections import deque
def split(iterable, function):
dq_true = deque()
dq_false = deque()
# deque - the fastest way to consume an iterator and append items
deque((
(dq_true if function(item) else dq_false).append(item) for item in iterable
), maxlen=0)
return dq_true, dq_false
Затем вы можете использовать функцию следующим образом:
lower, higher = split([0,1,2,3,4,5,6,7,8,9], lambda x: x < 5)
selected, other = split([0,1,2,3,4,5,6,7,8,9], lambda x: x in {0,4,9})
Если вы не в порядке с результатом deque
объект, вы можете легко преобразовать его в list
, set
что угодно (например list(lower)
). Преобразование происходит намного быстрее, чем построение списков напрямую.
Этот метод сохраняет порядок элементов, а также любые дубликаты.
Для исполнения попробуйте itertools
,
Модуль itertools стандартизирует базовый набор быстрых, эффективных с точки зрения памяти инструментов, которые полезны сами по себе или в сочетании. Вместе они образуют "алгебру итераторов", позволяющую быстро и эффективно создавать специализированные инструменты в чистом Python.
Смотрите itertools.ifilter или imap.
itertools.ifilter (предикат, повторяемый)
Создайте итератор, который отфильтровывает элементы от повторяемых, возвращая только те, для которых предикат равен True
Если список состоит из групп и прерывистых разделителей, вы можете использовать:
def split(items, p):
groups = [[]]
for i in items:
if p(i):
groups.append([])
groups[-1].append(i)
return groups
Использование:
split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
решение
from itertools import tee
def unpack_args(fn):
return lambda t: fn(*t)
def separate(fn, lx):
return map(
unpack_args(
lambda i, ly: filter(
lambda el: bool(i) == fn(el),
ly)),
enumerate(tee(lx, 2)))
тестовое задание
[even, odd] = separate(
lambda x: bool(x % 2),
[1, 2, 3, 4, 5])
print(list(even) == [2, 4])
print(list(odd) == [1, 3, 5])
Еще один ответ, короткий, но "злой" (из-за побочных эффектов понимания списка).
digits = list(range(10))
odd = [x.pop(i) for i, x in enumerate(digits) if x % 2]
>>> odd
[1, 3, 5, 7, 9]
>>> digits
[0, 2, 4, 6, 8]
Иногда вам не понадобится эта другая половина списка. Например:
import sys
from itertools import ifilter
trustedPeople = sys.argv[1].split(',')
newName = sys.argv[2]
myFriends = ifilter(lambda x: x.startswith('Shi'), trustedPeople)
print '%s is %smy friend.' % (newName, newName not in myFriends 'not ' or '')
Использование логической логики для присвоения данных двум массивам
>>> images, anims = [[i for i in files if t ^ (i[2].lower() in IMAGE_TYPES) ] for t in (0, 1)]
>>> images
[('file1.jpg', 33, '.jpg')]
>>> anims
[('file2.avi', 999, '.avi')]
Если вы настаиваете на умном, вы могли бы принять решение Уиндена и немного поддельную хитрость:
def splay(l, f, d=None):
d = d or {}
for x in l: d.setdefault(f(x), []).append(x)
return d
Предыдущие ответы, похоже, не удовлетворяют всех четырех моих обсессивно-компульсивных тиков:
- Будь как можно ленивее,
- Оценить исходный Iterable только один раз
- Вычисляйте предикат только один раз для каждого элемента
- Предоставьте красивые аннотации типов (для python 3.7)
Мое решение некрасивое, и я не думаю, что могу порекомендовать его, но вот оно:
def iter_split_on_predicate(predicate: Callable[[T], bool], iterable: Iterable[T]) -> Tuple[Iterator[T], Iterator[T]]:
deque_predicate_true = deque()
deque_predicate_false = deque()
# define a generator function to consume the input iterable
# the Predicate is evaluated once per item, added to the appropriate deque, and the predicate result it yielded
def shared_generator(definitely_an_iterator):
for item in definitely_an_iterator:
print("Evaluate predicate.")
if predicate(item):
deque_predicate_true.appendleft(item)
yield True
else:
deque_predicate_false.appendleft(item)
yield False
# consume input iterable only once,
# converting to an iterator with the iter() function if necessary. Probably this conversion is unnecessary
shared_gen = shared_generator(
iterable if isinstance(iterable, collections.abc.Iterator) else iter(iterable)
)
# define a generator function for each predicate outcome and queue
def iter_for(predicate_value, hold_queue):
def consume_shared_generator_until_hold_queue_contains_something():
if not hold_queue:
try:
while next(shared_gen) != predicate_value:
pass
except:
pass
consume_shared_generator_until_hold_queue_contains_something()
while hold_queue:
print("Yield where predicate is "+str(predicate_value))
yield hold_queue.pop()
consume_shared_generator_until_hold_queue_contains_something()
# return a tuple of two generators
return iter_for(predicate_value=True, hold_queue=deque_predicate_true), iter_for(predicate_value=False, hold_queue=deque_predicate_false)
Тестируя следующее, мы получаем результат ниже из операторов печати:
t,f = iter_split_on_predicate(lambda item:item>=10,[1,2,3,10,11,12,4,5,6,13,14,15])
print(list(zip(t,f)))
# Evaluate predicate.
# Evaluate predicate.
# Evaluate predicate.
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Evaluate predicate.
# Evaluate predicate.
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# [(10, 1), (11, 2), (12, 3), (13, 4), (14, 5), (15, 6)]
Понимание турбо-заряженного списка
set_good_vals = set(good_vals) # Speed boost.
good = [x for x in my_list if x in set_good_vals]
bad = [x for x in my_list if x not in set_good_vals]
Список понятен и понятен (в отличие от некоторых других умных ответов). Но это медленнее, чем некоторые другие ответы.
Однако, преобразовав
good_vals
к набору вы получите и то, и другое; четкость и скорость. Это победа.
Увеличение скорости составило 170% по сравнению с простым пониманием списка в модифицированной версии теста FunkySayu. Фактическое увеличение скорости будет зависеть от ваших данных.
Мой любимый рецепт для этого:
goodvals = set(goodvals)
good, bad = [], []
_ = [good.append(x) if x in goodvals else bad.append(x) for x in mylist]
Простой, быстрый и читаемый; таким, каким Python должен был быть.
- Делая
goodvals
вset
(который использует хэш-таблицу) вместоtuple
мы получаем супер быстрые поиски. - Каждый элемент в
mylist
проверяется только один раз. это помогает сделать это быстрее. _ =
это Pythonic способ заявить, что мы намеренно отбрасываем результат понимания списка. Это не ошибка.
(На основании комментария Дансалмо к этому ответу, потому что, похоже, он заслуживает отдельного ответа.)