Операция вычитания списка Python

Я хочу сделать что-то похожее на это:

>>> 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 Каков наилучший способ сделать это?

Ответ 1

Используйте понимание списка:

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

Если вы хотите использовать синтаксис - infix, вы можете просто сделать:

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   

Но если вам не нужны свойства списка (например, упорядочение), просто используйте наборы, как рекомендуют другие ответы.

Ответ 2

Используйте установить разницу

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

Или вы можете просто установить x и y, чтобы вам не приходилось делать никаких преобразований.

Ответ 3

Это операция "вычитания множества". Используйте для этого структуру данных.

В 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])

Ответ 4

Если проблемы с дублированием и упорядочением являются проблемой:

[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]

Ответ 5

Для многих случаев использования ответ, который вы хотите:

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

Это гибрид между ответом aaronasterling и answerSoup answer.

версия aaronasterling выполняет len(y) сравнение элементов для каждого элемента в x, поэтому требуется квадратичное время. Версия quantSoup использует наборы, поэтому для каждого элемента в x используется только один постоянный поиск по времени, но поскольку он преобразует как x, так и y в множества, он теряет порядок ваших элементов.

Преобразуя только y в набор и итерируя x по порядку, вы получаете лучшее из как линейного времени мира, так и сохранения порядка. *


Тем не менее, эта проблема по-прежнему имеет проблему с версией quantSoup: она требует, чтобы ваши элементы были хешируемыми. Это в значительной степени встроено в природу наборов. ** Если вы пытаетесь, например, вычесть список dicts из другого списка dicts, но список для вычитания большой, что вы делаете?

Если вы можете украсить свои ценности каким-то образом, что они хешируются, это решает проблему. Например, с плоским словарем, значения которого сами хешируются:

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

Если ваши типы немного сложнее (например, часто вы имеете дело с JSON-совместимыми значениями, которые являются хешируемыми или списками или dicts, значения которых рекурсивно одного типа), вы все равно можете использовать это решение. Но некоторые типы просто не могут быть преобразованы во что-нибудь хешируемое.


Если ваши элементы не являются и не могут быть сделаны хешируемыми, но они сопоставимы, вы можете, по крайней мере, получить лог-линейное время (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, для которых вы можете найти рецепты и сторонние модули. Но я думаю, что это проще.

** По умолчанию набор запросов - это постоянное время, так как все, что ему нужно сделать, это хэш-значение и посмотреть, есть ли запись для этого хэша. Если он не может присвоить значение, это не сработает.

Ответ 6

Поиск значений в наборах быстрее, чем просмотр их в списках:

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

Я считаю, что это будет немного лучше, чем:

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

Оба сохраняют порядок списков.

Ответ 7

Попробуйте это.

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
>>>

Ответ 8

Если списки допускают дублирование элементов, вы можете использовать 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]) ]

Ответ 9

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

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

Я думаю, что самый простой способ добиться этого - использовать 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]

Ответ 11

Другие решения имеют одну из нескольких проблем:

  1. Они не сохраняют порядок или
  2. Они не удаляют точное количество элементов, например для x = [1, 2, 2, 2] и y = [2, 2] они преобразуют y в 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 s общие средние значения big-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

Ответ 12

Я думаю это быстрее

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}

Ответ 13

В этом примере вычитаются два списка:

# 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))