Operação de subtração de lista Python

227

Eu quero fazer algo semelhante a isto:

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

Mas isso não é suportado pelas listas python. Qual é a melhor maneira de fazer isso?

sonhador
fonte
@ezdazuzena isso não é uma subtração. Essa é a diferença entre duas listas. Seu compartilhamento não é uma publicação desta pergunta.
Celik
1
O que [2, 2] - [2] deve retornar? []? [2]?
McKay
@ McKay [2,2] - [2] deve retornar [2]. [2,2] - [1,2,2,3] deve retornar []
Robino 04/07/19
Esta pergunta é sobre subtração de lista, mas a resposta aceita está mais próxima de definir subtração.
Robino
2
O que [2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] retornam e por quê? Deveria encontrar o 232 no meio e retornar 2142? ou deveria encontrar o primeiro de cada vez e retornar 1242? Ou alguma outra coisa? O que estou dizendo é que essas não são respostas óbvias e dependem da necessidade.
McKay

Respostas:

330

Use uma compreensão da lista:

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

Se você deseja usar a -sintaxe do infix, basta:

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

você pode usá-lo como:

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

Mas se você não precisar absolutamente de propriedades da lista (por exemplo, fazer pedido), use conjuntos como as outras respostas recomendam.

aaronasterling
fonte
10
@admica, não use listpara nomes de variáveis, pois sombreia o listconstrutor. Se você usa 'list', preceda-o com um sublinhado. Além disso, ao deixar cair o *, você quebrou meu código ...
aaronasterling
19
Se o fizer [1,1,2,2] - [1,2], receberá uma lista vazia. [1,1,2,2] - [2][1,1]Portanto, não é realmente lista subtração, é mais como "Lista de Lista X , sem elementos de conjunto Y " .
Alfred Zien
@AlfredZien o que ele disse
RetroCode 15/09
O método de compreensão da lista é muito mais lento (no meu exemplo) do que o método das diferenças definidas.
Redfiloux
1
@BarnabasSzabolcs: Isso não vai salvar nada, porque será convertido yem um setantes de cada verificação (que é um custo semelhante ao trabalho original). Você precisaria fazer yset = set(y)fora do listcomp, depois testar if item not in ysetou como um hack flagrante, o [item for yset in [set(y)] for item in x if item not in yset]que abusa dos listcomps aninhados para armazenar em cache o ysetone-liner. Uma solução de uma linha um pouco menos feia e com desempenho adequado seria usar list(itertools.filterfalse(set(y).__contains__, x))porque o argumento para filterfalseé construído apenas uma vez.
ShadowRanger
259

Usar diferença definida

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

Ou você pode apenas ter conjuntos xey para não precisar fazer conversões.

quantumSoup
fonte
50
isso perderá qualquer pedido. Isso pode ou não importar, dependendo do contexto.
Aaronasterling
63
Isso também perderá quaisquer possíveis duplicatas que possam precisar / desejar manutenção.
Opala
Eu receboTypeError: unhashable type: 'dict'
Havnar 2/17/17
Esta é a maneira mais rápida nos casos em que as listas que estão sendo comparados são grandes
JqueryToAddNumbers
2
Se a ordem e duplicatas dos itens da lista não são importantes para o contexto, essa é uma ótima resposta, além de muito legível.
Watt Iamsuri
37

Essa é uma operação de "subtração de conjunto". Use a estrutura de dados definida para isso.

No Python 2.7:

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

Resultado:

>>> print x - y
set([0, 8, 2, 4, 6])
Papai Noel
fonte
1
list (set ([1,2,3,4,5]) - set ([1,2,3])) = [4, 5] para listar cada uma para definir primeiro e depois subtrair (ou diff unidirecional ) e voltar à lista.
gseattle
2
Não é bom se você deseja manter a ordem dos itens originais do conjunto x.
Zahran
34

se itens duplicados e pedidos forem problema:

[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]
nguyên
fonte
2
Isso funciona, embora seja em O(m * n)tempo de execução (e eu me encolho sempre que um listcomp inclui efeitos colaterais); você pode aprimorá-lo usandocollections.Counter para obter o O(m + n)tempo de execução.
ShadowRanger
Estou tendo dificuldade para entender isso, alguém pode explicar?
Anushka 23/10/19
20

Para muitos casos de uso, a resposta que você deseja é:

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

Este é um híbrido entre a resposta de aaronasterling e a resposta quântica .

A versão de aaronasterling faz len(y)comparações de itens para cada elemento x, portanto leva tempo quadrático. A versão do quantumSoup usa conjuntos, portanto, faz uma única pesquisa de conjunto em tempo constante para cada elemento em x- mas, porque converte ambos x e yem conjuntos, perde a ordem dos seus elementos.

Ao converter apenas yem um conjunto e iterar xem ordem, você obtém o melhor dos dois mundos - tempo linear e preservação da ordem. *


No entanto, isso ainda tem um problema da versão do quantumSoup: requer que seus elementos sejam laváveis. Isso é bastante incorporado à natureza dos conjuntos. ** Se você está tentando, por exemplo, subtrair uma lista de dictos de outra lista de dictos, mas a lista a subtrair é grande, o que você faz?

Se você pode decorar seus valores de alguma maneira que eles sejam laváveis, isso resolve o problema. Por exemplo, com um dicionário simples cujos valores são eles mesmos hashable:

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

Se seus tipos forem um pouco mais complicados (por exemplo, com frequência, você está lidando com valores compatíveis com JSON, que são hasháveis ​​ou com listas ou dictos cujos valores são recursivamente do mesmo tipo), você ainda pode usar esta solução. Mas alguns tipos simplesmente não podem ser convertidos em nada que possa ser lavado.


Se seus itens não são, e não podem ser fabricados, laváveis, mas são comparáveis, você pode obter pelo menos tempo linear de log ( O(N*log M)que é muito melhor que o O(N*M)tempo da solução da lista, mas não tão bom quanto) o O(N+M)horário da solução definida) classificando e usando 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)]

Se seus itens não são laváveis ​​nem comparáveis, você fica com a solução quadrática.


* Observe que você também pode fazer isso usando um par de OrderedSetobjetos, para os quais pode encontrar receitas e módulos de terceiros. Mas acho que isso é mais simples.

** O motivo pelo qual as pesquisas de conjunto são constantes é que tudo o que você precisa fazer é o valor do hash e verificar se há uma entrada para esse hash. Se não puder misturar o valor, isso não funcionará.

abarnert
fonte
7

A pesquisa de valores em conjuntos é mais rápida do que a pesquisa em listas:

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

Acredito que isso será dimensionado um pouco melhor do que:

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

Ambos preservam a ordem das listas.

rudolfbyker
fonte
Ele armazenará em cache set(y)e não será convertido yem um novo conjunto em cada loop? Caso contrário, você resposta necessidade de abarnert: ys = set(y); [i for i in x if i not in ys].
Jacktose 23/05/19
2
Alguns testes aproximados sugerem que if i not in set(y)leva 25% mais tempo do que if i not in y(onde yestá uma lista). A pré-conversão do conjunto leva 55% menos tempo. Testado com bastante curto xe y, mas as diferenças devem ser mais pronunciadas com o comprimento, se houver.
Jacktose 23/05/19
1
@ Jacktose: Sim, esta solução faz mais trabalho, porque precisa iterar e hash todos os elementos de ypara cada elemento de x; a menos que a comparação de igualdade seja realmente cara em relação ao cálculo de hash, isso sempre será perdido item not in y.
ShadowRanger
@ShadowRanger que faz sentido. Se a conversão definida fosse uma maneira mais rápida e confiável de fazer essa verificação, você pensaria que o compilador sempre faria a verificação dessa maneira.
Jacktose 10/09/19
5

Se as listas permitirem elementos duplicados, você poderá usar o Contador de coleções:

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

Se você precisar preservar a ordem dos elementos de x:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]
Alain T.
fonte
Isso é bom, embora perca a ordem; consertar isso é um pouco mais complicado .
ShadowRanger
@ShadowRanger, é de fato. mas só um pouco.
Alain T.
Não me importo, eu vou estremecer nos listcomps com cache e efeitos colaterais (embora suponha que a combinação dos dois remova os efeitos colaterais visíveis externamente?). :-)
ShadowRanger
Além disso, esse código não funcionará como escrito; Counter.subtractnão remove elementos com valor zero ( -e remove -=, mas não subtract); portanto, você nunca para de remover elementos. Você deseja substituir not v in cpor not c[v](que retorna zero para elementos inexistentes, para que você possa testar com segurança o retorno para "zerar" via not).
ShadowRanger
@ShadowRanger, Good catch! Corrigido agora.
Alain T.
3

Eu acho que a maneira mais fácil de conseguir isso é usando 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]
Loochie
fonte
3

As outras soluções têm um de alguns problemas:

  1. Eles não preservam a ordem, ou
  2. Eles não removem uma contagem precisa de elementos, por exemplo, para x = [1, 2, 2, 2]e y = [2, 2]convertem yem a set, e removem todos os elementos correspondentes (deixando [1]apenas) ou removem um de cada elemento exclusivo (deixando [1, 2, 2]), quando o comportamento adequado seria remover 2duas vezes, saindo [1, 2]ou
  3. Eles O(m * n)funcionam, onde uma solução ideal pode O(m + n)funcionar

Alain estava no caminho certoCounter para resolver os nºs 2 e 3, mas essa solução perderá a ordem. A solução que preserva a ordem (removendo as primeiras ncópias de cada valor para nrepetições nos listvalores a serem removidos) é:

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.

Experimente online!

Para remover as últimas cópias de cada elemento, basta alterar o forloop para for val in reversed(x):e adicionar out.reverse()imediatamente após sair do forloop.

Construir o Counteris O(n)em termos de ycomprimento, iterar xé O(n)em termos de xcomprimento, e Counteros testes e mutações de membros são O(1), enquanto list.appendsão amortizados O(1)(um dado appendpode ser O(n), mas para muitos appends, a média geral do grande O, O(1)pois cada vez menos deles exigem uma realocação); portanto, o trabalho geral realizado é O(m + n).

Você também pode testar para determinar se havia algum elemento yque não foi removido xpelo teste:

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts
ShadowRanger
fonte
Nota: Este não requerem os valores a serem Hashable, mas qualquer solução que não requer objetos Hashable ou não é de propósito geral (por exemplo, pode contar ints em matriz de comprimento fixo) ou tem de fazer mais do O(m + n)trabalho (por exemplo, a próxima melhor grande -O seria criar uma classificação listde pares únicos de valor / contagem, transformando O(1) dictpesquisas em pesquisas O(log n)binárias; você precisaria de valores únicos com suas contagens, não apenas valores não únicos classificados, porque, caso contrário, estaria pagando O(n)custos para remover o elementos da classificação list).
ShadowRanger
2

Tente isso.

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
>>>
user3435376
fonte
1

A resposta fornecida por @aaronasterling parece boa, no entanto, não é compatível com a interface padrão da lista: x = MyList(1, 2, 3, 4) vs x = MyList([1, 2, 3, 4]). Assim, o código abaixo pode ser usado como um mais amigável à lista de 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])

Exemplo:

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

Eu acho que isso é mais rápido:

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}
Eds_k
fonte
Isso não é subtração. De fato, essa é a diferença simétrica entre duas listas.
Parth Chauhan
Além disso, este só funciona para objetos Hashable dentro das listas
zhukovgreen
-1

Este exemplo subtrai duas listas:

# 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))
Joao Nicolau
fonte
8
Evite isso, é O (N ^ 2)
Alexander - Restabelece Monica