Операція віднімання списку Python


227

Я хочу зробити щось подібне до цього:

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> x  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]  
>>> y = [1,3,5,7,9]  
>>> y  
[1, 3, 5, 7, 9]  
>>> y - x   # (should return [2,4,6,8,0])

Але це не підтримується списками python Який найкращий спосіб зробити це?


@ezdazuzena це не субстракція. Це різниця між двома списками. Ваш обмін не є дублюванням цього питання.
Челік

1
Що слід [2, 2] - [2] повернути? []? [2]?
Маккей

@McKay [2,2] - [2] повинен повернутися [2]. [2,2] - [1,2,2,3] повинні повернутися []
Робіно

Це питання стосується віднімання списку, але прийнята відповідь ближче до заданого віднімання.
Робіно

2
Що слід [2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] повернути, і чому? Чи повинен він знайти 232 посередині і повернути 2142? або він повинен кожного разу знайти перший і повернути 1242? Або щось інше? Я говорю, що це не очевидні відповіді і залежать від потреби.
Маккей

Відповіді:


330

Використовуйте розуміння списку:

[item for item in x if item not in y]

Якщо ви хочете використовувати -синтаксис інфіксації, ви можете просто зробити:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(args)

    def __sub__(self, other):
        return self.__class__(*[item for item in self if item not in other])

Ви можете використовувати його так:

x = MyList(1, 2, 3, 4)
y = MyList(2, 5, 2)
z = x - y   

Але якщо вам абсолютно не потрібні властивості списку (наприклад, замовлення), просто використовуйте набори, як рекомендують інші відповіді.


10
@admica, не використовуйте listдля змінних імен, оскільки це затінює listконструктор. Якщо ви використовуєте "список", будь-ласка, передуйте цьому підкреслення. Також, скинувши *, ви зламали мій код ...
aaronasterling

19
Якщо ви це зробите, [1,1,2,2] - [1,2]ви отримаєте порожній список. [1,1,2,2] - [2]дає [1,1]Таким чином , це на самому ділі не список віднімання, це більше схоже на «Список зі списку X без елементів з безлічі Y » .
Альфред Зієн

@AlfredZien те, що він сказав
RetroCode

Метод розуміння списку набагато повільніше (на моєму прикладі), ніж метод встановленої різниці.
redfiloux

1
@BarnabasSzabolcs: Це не врятує річ, тому що вона буде перетворити yдо setперш , ніж кожен чек (який схожий вартість оригіналу). Вам потрібно буде або yset = set(y)за межами listcomp, потім тестувати if item not in yset, або, як кричущий хак, робити [item for yset in [set(y)] for item in x if item not in yset]який зловживає вкладеними listcomps, щоб кешувати ysetяк однолінійний . Трохи менш потворне однолінійне рішення, яке працює належним чином, було б корисним, list(itertools.filterfalse(set(y).__contains__, x))тому що аргумент filterfalseбудується лише один раз.
ShadowRanger

259

Використовуйте задану різницю

>>> z = list(set(x) - set(y))
>>> z
[0, 8, 2, 4, 6]

Або ви можете просто встановити набір x і y, тому вам не потрібно робити перетворення.


50
це втратить будь-яке впорядкування. Це може або не має значення залежно від контексту.
aaronasterling

63
Це також втратить всі можливі дублікати, які можуть потребувати / хочуть підтримувати.
Опал

Я отримуюTypeError: unhashable type: 'dict'
Хавнар

Це набагато швидше у випадках, коли списки, що порівнюються, великі
JqueryToAddNumbers

2
Якщо замовлення та дублікати елементів у списку не важливі для контексту, це чудова відповідь плюс він дуже читабельний.
Ватт Ямсурі

37

Це операція "встановлення віднімання". Використовуйте для цього встановлену структуру даних.

У Python 2.7:

x = {1,2,3,4,5,6,7,8,9,0}
y = {1,3,5,7,9}
print x - y

Вихід:

>>> print x - y
set([0, 8, 2, 4, 6])

1
list (set ([1,2,3,4,5]) - set ([1,2,3])) = [4, 5], так що списки кожного слід встановити спочатку, а потім відняти (або в одну сторону ) і повернутися до списку.
gseattle

2
Не добре, якщо ви хочете підтримувати оригінальний порядок елементів набору x.
Захран

34

якщо проблеми з дублікатами та замовленнями:

[i for i in a if not i in b or b.remove(i)]

a = [1,2,3,3,3,3,4]
b = [1,3]
result: [2, 3, 3, 3, 4]

2
Це працює, хоча це час O(m * n)виконання (і я переглядаю кожен раз, коли список компіляцій включає побічні ефекти); ви можете покращити його, використовуючиcollections.Counter для отримання O(m + n)час виконання.
ShadowRanger

Мені важко це зрозуміти, може хтось пояснить?
Аннушка

20

У багатьох випадках використання потрібна відповідь:

ys = set(y)
[item for item in x if item not in ys]

Це гібрид між відповіддю aaronasterling в і відповідь quantumSoup в .

Версія aaronasterling робить len(y)порівняння елементів для кожного елемента x, тому це займає квадратичний час. версія використовує набори quantumSoup, так що він робить одну постійного час набору пошуку для кожного елемента в xбуті, так як він перетворює як x іy в наборах, вона втрачає порядок ваших елементів.

Перетворюючи лише yнабір і повторюючи xпорядок, ви отримуєте найкраще з обох світів - лінійного часу та збереження порядку. *


Однак це все ще має проблему з версією QuantumSoup: Це вимагає, щоб ваші елементи були перебірними. Це в значній мірі вбудовано в природу множин. ** Якщо ви намагаєтесь, наприклад, відняти список диктовок з іншого списку диктів, але список для віднімання великий, що ви робите?

Якщо ви можете прикрасити свої цінності таким чином, щоб вони були зручними, це вирішить проблему. Наприклад, з плоским словником, значення якого самі є доступними:

ys = {tuple(item.items()) for item in y}
[item for item in x if tuple(item.items()) not in ys]

Якщо ваші типи трохи складніші (наприклад, часто ви маєте справу з сумісними з JSON значеннями, які є хешбек, або списками або диктантами, значення яких рекурсивно одного типу), ви все одно можете використовувати це рішення. Але деякі типи просто неможливо перетворити на що-небудь доступне.


Якщо ваші предмети не є і їх неможливо зробити хешируемими, але вони порівнянні, ви можете принаймні отримати лінійний час журналу ( O(N*log M)що набагато краще, ніж O(N*M)час рішення списку, але не так добре, як O(N+M)час заданого розчину) шляхом сортування і з допомогою bisect:

ys = sorted(y)
def bisect_contains(seq, item):
    index = bisect.bisect(seq, item)
    return index < len(seq) and seq[index] == item
[item for item in x if bisect_contains(ys, item)]

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


* Зауважте, що ви могли також зробити це за допомогою пари OrderedSetоб'єктів, для яких ви можете знайти рецепти та сторонні модулі. Але я думаю, що це простіше.

** Причина, що шукає пошуки - це постійний час, полягає в тому, що все, що потрібно зробити, - це хеш-значення і подивитися, чи є запис для цього хешу. Якщо він не може хеш-значення, це не працюватиме.


7

Пошук значень у наборах швидше, ніж пошук у списках:

[item for item in x if item not in set(y)]

Я вважаю, що це буде дещо кращим, ніж:

[item for item in x if item not in y]

Обидва зберігають порядок списків.


Чи буде це кешування, set(y)а не перетвориться yна новий набір у кожному циклі? В іншому випадку, ви б відповідь потрібно abarnert в: ys = set(y); [i for i in x if i not in ys].
Джектоз

2
Деякі грубі тестування припускають, що це if i not in set(y)займає 25% довше, ніж if i not in y(де yсписок). Попереднє перетворення набору займає на 55% менше часу. Випробовується досить коротко xі y, але відмінності повинні бути більш вираженими з довжиною, якщо що.
Джектоз

1
@Jacktose: Так, це рішення робить більшу роботу, оскільки воно має повторювати та хешувати кожен елемент yдля кожного елемента x; якщо порівняння рівності не є дійсно дорогим по відношенню до обчислення хешу, це завжди втрачає очевидну item not in y.
ShadowRanger

@ShadowRanger, що має сенс. Якщо встановлення конверсій було надійно швидшим способом зробити цю перевірку, ви вважаєте, що компілятор завжди завжди перевірятиме так.
Джектоз

5

Якщо списки дозволяють повторювати елементи, ви можете використовувати Counter з колекцій:

from collections import Counter
result = list((Counter(x)-Counter(y)).elements())

Якщо вам потрібно зберегти порядок елементів з x:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]

Це добре, хоча втрачає впорядкованість; виправлення, яке трохи складніше .
ShadowRanger

@ShadowRanger, це справді. але трохи.
Ален Т.

Не заважайте мені, я просто збираюся здригатися у listcomps з кешуванням та побічними ефектами (хоча, мабуть, комбінація двох видаляє зовнішні видимі побічні ефекти?). :-)
ShadowRanger

Також цей код не працюватиме як написано; Counter.subtractне видаляє нульові елементи ( -і -=робити, але ні subtract), тому ви ніколи не припиняйте видаляти елементи. Ви хочете замінити not v in cна not c[v](який повертає нуль для неіснуючих елементів, тому ви можете сміливо перевірити повернення на "нульовість" через not).
ShadowRanger

@ShadowRanger, Добрий улов! Виправлено це зараз.
Ален Т.

3

Інші рішення мають одну з декількох проблем:

  1. Вони не зберігають порядок, або
  2. Вони не видаляють точну кількість елементів, наприклад, для x = [1, 2, 2, 2]і y = [2, 2]перетворюються yв a set, а також видаляють всі відповідні елементи ( [1]лише залишившись ) або видаляючи один з кожного унікального елемента (залишаючи [1, 2, 2]), коли правильною поведінкою було б видалення 2двічі, залишаючи [1, 2], або
  3. Вони роблять O(m * n)роботу, де оптимальне рішення може зробити O(m + n)роботу

Ален був на правильному шляху,Counter щоб вирішити №2 та №3, але це рішення втратить замовлення. Рішення, що зберігає порядок (видалення перших nкопій кожного значення для nповторень у listзначеннях для видалення):

from collections import Counter

x = [1,2,3,4,3,2,1]  
y = [1,2,2]  
remaining = Counter(y)

out = []
for val in x:
    if remaining[val]:
        remaining[val] -= 1
    else:
        out.append(val)
# out is now [3, 4, 3, 1], having removed the first 1 and both 2s.

Спробуйте в Інтернеті!

Щоб видалити останні копії кожного елемента, просто змініть forцикл на for val in reversed(x):та додайте out.reverse()відразу після виходу з forциклу.

Побудова Counterє O(n)з точки зору yтривалості, ітерація x- O(n)з точки зору xтривалості, а Counterтестування та мутація членства є O(1), поки list.appendамортизується O(1)(дана інформація appendможе бути O(n), але для багатьох appendс загальна величина середнього величини O O(1)менше, ніж менша та менша. з них вимагає перерозподілу), тому загальна виконана робота - це O(m + n).

Ви також можете протестувати, щоб визначити, чи були елементи, yякі не були видалені з xдопомогою тестування:

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts

Примітка. Для цього потрібні величини, які є доступними для перегляду, але будь-яке рішення, яке не потребує об'єктів, що мають хешируемость, або не має загального призначення (наприклад, може рахувати ints в масив фіксованої довжини) або має робити більше, ніж O(m + n)працювати (наприклад, наступний найкращий великий -О було б зробити сортування listунікальних пар / значення / підрахунку, змінивши пошукові O(1) dictзапити на O(log n)двійкові пошуки; вам знадобляться унікальні значення з їх підрахунками, а не просто відсортовані не унікальні значення, бо в іншому випадку ви будете платити O(n)витрати на видалення елементи з відсортованого list).
ShadowRanger

2

Спробуйте це.

def subtract_lists(a, b):
    """ Subtracts two lists. Throws ValueError if b contains items not in a """
    # Terminate if b is empty, otherwise remove b[0] from a and recurse
    return a if len(b) == 0 else [a[:i] + subtract_lists(a[i+1:], b[1:]) 
                                  for i in [a.index(b[0])]][0]

>>> x = [1,2,3,4,5,6,7,8,9,0]
>>> y = [1,3,5,7,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0]
>>> x = [1,2,3,4,5,6,7,8,9,0,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0, 9]     #9 is only deleted once
>>>

2

Я думаю, що найпростіший спосіб досягти цього, використовуючи set ().

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> y = [1,3,5,7,9]  
>>> list(set(x)- set(y))
[0, 2, 4, 6, 8]

1

Відповідь надається @aaronasterling виглядає добре, однак, він не сумісний з інтерфейсом за замовчуванням списку: x = MyList(1, 2, 3, 4)проти x = MyList([1, 2, 3, 4]). Таким чином, наведений нижче код може використовуватися як більш зручний список пітонів:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(*args)

    def __sub__(self, other):
        return self.__class__([item for item in self if item not in other])

Приклад:

x = MyList([1, 2, 3, 4])
y = MyList([2, 5, 2])
z = x - y

0

Я думаю, що це швидше:

In [1]: a = [1,2,3,4,5]

In [2]: b = [2,3,4,5]

In [3]: c = set(a) ^ set(b)

In [4]: c
Out[4]: {1}

Це не віднімання. Насправді це симетрична різниця між двома списками.
Parth Chauhan

Більше того, це працює лише для об'єктів, що переміщуються, всередині списків
zhukovgreen

-1

Цей приклад віднімає два списки:

# List of pairs of points
list = []
list.append([(602, 336), (624, 365)])
list.append([(635, 336), (654, 365)])
list.append([(642, 342), (648, 358)])
list.append([(644, 344), (646, 356)])
list.append([(653, 337), (671, 365)])
list.append([(728, 13), (739, 32)])
list.append([(756, 59), (767, 79)])

itens_to_remove = []
itens_to_remove.append([(642, 342), (648, 358)])
itens_to_remove.append([(644, 344), (646, 356)])

print("Initial List Size: ", len(list))

for a in itens_to_remove:
    for b in list:
        if a == b :
            list.remove(b)

print("Final List Size: ", len(list))

8
Уникайте цього, це О (N ^ 2)
Олександр - Відновіть Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.