How can I partition (split up, divide) a list based on a condition?
Как я могу разделить (split up, divide) список на основе условия?
У меня есть некоторый код, подобный:
good = [x for x in mylist if x in goodvals] bad = [x for x in mylist if x notin goodvals]
Цель состоит в том, чтобы разделить содержимое mylist на два других списка, в зависимости от того, соответствуют они условию или нет.
Как я могу сделать это более элегантно? Могу ли я избежать выполнения двух отдельных итерацийmylist? Могу ли я повысить производительность, поступая таким образом?
Переведено автоматически
Ответ 1
Выполните итерацию вручную, используя условие для выбора списка, к которому будет добавлен каждый элемент:
good, bad = [], [] for x in mylist: (bad, good)[x in goodvals].append(x)
Ответ 2
good = [x for x in mylist if x in goodvals] bad = [x for x in mylist if x notin 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() notin IMAGE_TYPES]
Опять же, это прекрасно!
Использование наборов может привести к небольшому улучшению производительности, но это тривиальная разница, и я нахожу, что понимание списка намного проще для чтения, и вам не нужно беспокоиться о том, что порядок будет нарушен, дубликаты будут удалены и так далее.
На самом деле, я могу сделать еще один шаг "назад" и просто использовать простой цикл for:
images, anims = [], []
for f in files: if f.lower() in IMAGE_TYPES: images.append(f) else: anims.append(f)
Список a-понимание или использование set() работает нормально, пока вам не понадобится добавить какую-либо другую проверку или другую часть логики - допустим, вы хотите удалить все 0-байтовые jpeg, вы просто добавляете что-то вроде..
if f[1] == 0: continue
Ответ 3
Вот подход ленивого итератора:
from itertools import tee
defsplit_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 ifnot p)
Он оценивает условие один раз для каждого элемента и возвращает два генератора, первый выдает значения из последовательности, где условие истинно, другой, где оно ложно.
Поскольку он ленивый, вы можете использовать его на любом итераторе, даже бесконечном:
from itertools import count, islice
defis_prime(n): return n > 1andall(n % i for i in xrange(2, n))
Хотя обычно подход, не требующий ленивого возврата списка, лучше:
defsplit_on_condition(seq, condition): a, b = [], [] for item in seq: (a if condition(item) else b).append(item) return a, b
Редактировать: Для вашего более конкретного использования разделения элементов на разные списки по некоторому ключу, вот общая функция, которая это делает:
DROP_VALUE = lambda _:_ defsplit_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)
Проблема со всеми предлагаемыми решениями заключается в том, что он будет сканировать и применять функцию фильтрации дважды. Я бы сделал простую небольшую функцию, подобную этой:
defsplit_into_two_lists(lst, f): a = [] b = [] for elem in lst: if f(elem): a.append(elem) else: b.append(elem) return a, b
Таким образом, вы ничего не обрабатываете дважды, а также не повторяете код.