Удалить все элементы, которые встречаются в одном списке из другого


367

Допустим, у меня есть два списка, l1и l2. Я хочу выполнить l1 - l2, который возвращает все элементы l1не в l2.

Я могу думать о подходе наивного цикла, но это будет действительно неэффективно. Каков питонный и эффективный способ сделать это?

В качестве примера, если у меня есть l1 = [1,2,6,8] and l2 = [2,3,5,8], l1 - l2должен вернуться[1,6]


12
Просто совет: PEP8 утверждает, что строчную букву «L» не следует использовать, потому что она выглядит слишком похоже на 1.
spelchekr

2
Согласен. Я прочитал весь этот вопрос и ответы на вопрос, почему люди продолжали использовать одиннадцать и двенадцать. Только когда я прочитал комментарий @spelchekr, это имело смысл.
Роблайн


@JimG. Датафрейм и список не одно и то же.
снижение активности

Ответы:


492

В Python есть языковая функция, называемая списками, которая идеально подходит для того, чтобы сделать подобные вещи чрезвычайно простыми. Следующий оператор делает именно то, что вы хотите, и сохраняет результат в l3:

l3 = [x for x in l1 if x not in l2]

l3будет содержать [1, 6].


8
Очень питонический; Мне это нравится! Насколько это эффективно?
фэндом

2
Я считаю, что это весьма эффективно, и его преимущество заключается в том, что он чрезвычайно удобочитаем и ясен в отношении того, чего вы пытаетесь достичь. Я наткнулся на сообщение в блоге, которое может вас заинтересовать в отношении эффективности: blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
Пончик

6
@fandom: само понимание списка довольно эффективно (хотя понимание генератора может быть более эффективным, если не дублировать элементы в памяти), но inоператор не так эффективен в списке. inв списке O (n), а inв множестве O (1). Тем не менее, пока вы не получите тысячи или более элементов, вы вряд ли заметите разницу.
Даниэль Приден

1
l3 = [x for x in l1 if x not in set(l2)]? Уверен, если set(l2)бы позвонили не раз.
Danosaure

5
Вы также можете просто установить l2s = set(l2)и затем сказать l3 = [x for x in l1 if x not in l2s]. Чуть проще.
spelchekr

149

Одним из способов является использование наборов:

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])

58
Это также удалит дубликаты l1, что может быть нежелательным побочным эффектом.
любезно

37
..и потерять порядок элементов (если порядок важен).
Danosaure

3
Я просто хочу добавить, что я сравнил это с принятым ответом, и он был более производительным в 3 раза timeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985 timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.04106225999998969. Поэтому, если производительность является значительным фактором, этот ответ может быть более уместным (а также, если вам не
нужны

38

В качестве альтернативы вы также можете использовать filterлямбда-выражение для получения желаемого результата. Например:

>>> l1 = [1,2,6,8]
>>> l2 = set([2,3,5,8])

#     v  `filter` returns the a iterator object. Here I'm type-casting 
#     v  it to `list` in order to display the resultant value
>>> list(filter(lambda x: x not in l2, l1))
[1, 6]

Сравнение производительности

Здесь я сравниваю эффективность всех ответов, упомянутых здесь. Как и ожидалось, операция на set базе Arkku является самой быстрой.

PS: set не поддерживает порядок и удаляет дубликаты элементов из списка. Следовательно, не используйте набор разностей, если вам нужно что-то из этого.


32

Расширяя ответ Donut и другие ответы здесь, вы можете получить еще лучшие результаты, используя понимание генератора вместо понимания списка, и используя setструктуру данных (так как inоператор - O (n) в списке, но O (1) на съемочной площадке).

Итак, вот функция, которая будет работать для вас:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

Результатом будет итерация, которая будет лениво извлекать отфильтрованный список. Если вам нужен реальный объект списка (например, если вам нужно сделать len()результат для результата), то вы можете легко построить список следующим образом:

filtered_list = list(filter_list(full_list, excludes))

29

Используйте тип набора Python. Это было бы самым Pythonic. :)

Кроме того, поскольку он является нативным, он также должен быть наиболее оптимизированным.

Видеть:

http://docs.python.org/library/stdtypes.html#set

http://docs.python.org/library/sets.htm (для старых питонов)

# Using Python 2.7 set literal format.
# Otherwise, use: l1 = set([1,2,6,8])
#
l1 = {1,2,6,8}
l2 = {2,3,5,8}
l3 = l1 - l2

5
При использовании наборов следует отметить, что выходные данные упорядочены, то есть {1,3,2} становится {1,2,3} и {"A", "C", "B"} становится {"A", "B", "C"}, и вы, возможно, не хотите иметь это.
Пабло Рейес

2
этот метод не будет работать, если список l1содержит повторяющиеся элементы.
августа

10

используйте Set Comppresions {x для x в l2} или set (l2), чтобы получить set, затем используйте List Compreatsions, чтобы получить список

l2set = set(l2)
l3 = [x for x in l1 if x not in l2set]

тестовый код:

import time

l1 = list(range(1000*10 * 3))
l2 = list(range(1000*10 * 2))

l2set = {x for x in l2}

tic = time.time()
l3 = [x for x in l1 if x not in l2set]
toc = time.time()
diffset = toc-tic
print(diffset)

tic = time.time()
l3 = [x for x in l1 if x not in l2]
toc = time.time()
difflist = toc-tic
print(difflist)

print("speedup %fx"%(difflist/diffset))

Результат теста производительности:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    

1
l2set = set( l2 )вместоl2set = { x for x in l2 }
cz

1
Хорошая душа! Но следует помнить, что он работает только с объектами, которые можно хэшировать.
Ээрик Свен Пуудист

7

Альтернативное решение:

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])

2
Есть ли преимущество в использовании этого метода? Похоже, что это сложнее и труднее читать без особой пользы.
skrrgwasme

Это может показаться сложным. Reduce очень гибкий и может быть использован для многих целей. Это известно как сгиб. уменьшить на самом деле сложнее. Предположим, что вы хотите добавить более сложные вещи, тогда это будет возможно в этой функции, но понимание списка, которое является выбранным лучшим ответом, даст вам только результат того же типа, то есть списка и, вероятно, той же длины, в то время как со складками вы могли бы также измените тип вывода. en.wikipedia.org/wiki/Fold_%28higher-order_function%29 . Это решение n * m или менее сложное. Другие могут или не могут быть лучше, хотя.
Акшай Хазари

1
уменьшить (функция, список, начальный аккумулятор (который может быть любого типа))
Акшай Хазари
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.