Видаліть усі елементи, що зустрічаються в одному списку, з іншого


365

Скажімо, у мене є два списки, 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. Рамка даних та список - це не одне і те ж.
зниження активності

Відповіді:


491

У 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-comсаnces
Donut

6
@fandom: розуміння списку саме по собі є досить ефективним (хоча розуміння генератора може бути більш ефективним, якщо не дублювати елементи в пам'яті), але inоператор не такий ефективний у списку. inу списку - O (n), тоді як inна множині - O (1). Однак, поки ви не отримаєте тисячі елементів і більше, навряд чи ви помітите різницю.
Даніель Приден

1
l3 = [x for x in l1 if x not in set(l2)]? Я впевнений, якби set(l2)дзвонили не раз.
Даносаура

5
Ви також можете просто встановити, l2s = set(l2)а потім сказати l3 = [x for x in l1 if x not in l2s]. Трохи легше.
спелчекр

149

Одним із способів є використання наборів:

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

58
Це також видалить дублікати з l1, що може бути небажаним побічним ефектом.
kindall

37
..і втратити порядок елементів (якщо порядок важливий).
Даносаура

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. Тож якщо продуктивність є важливим фактором, ця відповідь може бути більш доречною (а також якщо вам не байдуже дублікати чи замовлення)
wfgeo

37

Як альтернативу, ви також можете використовувати 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 базі Арку є найшвидшою.

PS: set не підтримує порядок і видаляє повторювані елементи зі списку. Отже, не використовуйте різницю, якщо вам потрібно щось із цього.


32

Розширюючи відповідь Пончика та інші відповіді тут, ви можете отримати ще кращі результати, використовуючи розуміння генератора замість розуміння списку та використовуючи 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. Це було б найбільш піфонічно. :)

Крім того, оскільки він рідний, він повинен бути і найбільш оптимізованим методом.

Подивитися:

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

http://docs.python.org/library/sets.htm (для старшого python)

# 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включає повторювані елементи.
jdhao

10

використовуйте Встановити розуміння {x для x в l2} або set (l2), щоб встановити, а потім використовуйте Список Поняття, щоб отримати список

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

Це може здатися складним. Редукція дуже гнучка і може використовуватися для багатьох цілей. Він відомий як складка. зменшити насправді скласти. Припустимо, ви хочете додати в нього більш складні речі, тоді це буде можливо в цій функції, але розуміння списку, який є найкращою обраною відповіддю, отримає лише вихід одного типу, тобто список і, ймовірно, однакової довжини, а зі складками ви могли б змінити також тип виводу. en.wikipedia.org/wiki/Fold_%28higher-order_function%29 . Це рішення має n * m або меншу складність. Інші можуть бути, а можуть і не бути кращими.
Акшай Хазарі

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