Python encontra elementos em uma lista que não estão na outra [duplicado]

135

Preciso comparar duas listas para criar uma nova lista de elementos específicos encontrados em uma lista, mas não na outra. Por exemplo:

main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 

Desejo percorrer a lista_1 e acrescentar à main_list todos os elementos da lista_2 que não foram encontrados na lista_1.

O resultado deve ser:

main_list=["f", "m"]

Como posso fazer isso com python?

CosimoCD
fonte
2
Você está procurando elementos list_2que não aparecem em nenhum lugar list_1ou elementos list_2que não estão presentes no mesmo índice em list_1?
Patrick Haugh

Respostas:

96

TL; DR:
SOLUÇÃO (1)

import numpy as np
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`

SOLUÇÃO (2) Você deseja uma lista ordenada

def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans
main_list = setdiff_sorted(list_2,list_1)




EXPLICAÇÕES:
(1) Você pode usar NumPy do setdiff1d( array1, array2, assume_unique= False).

assume_uniquepergunta ao usuário se as matrizes já são únicas.
Se False, então os elementos exclusivos são determinados primeiro.
Se True, a função assumirá que os elementos já são exclusivos E a função ignorará a determinação dos elementos exclusivos.

Isso resulta os valores exclusivos em array1que não são nos array2. assume_uniqueé Falsepor padrão.

Se você está preocupado com os elementos exclusivos (com base na resposta de Chinny84 ), basta usar (onde assume_unique=False=> o valor padrão):

import numpy as np
list_1 = ["a", "b", "c", "d", "e"]
list_2 = ["a", "f", "c", "m"] 
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`


(2) Para quem deseja que as respostas sejam classificadas, criei uma função personalizada:

import numpy as np
def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans

Para obter a resposta, execute:

main_list = setdiff_sorted(list_2,list_1)

NOTAS LATERAIS:
(a) A solução 2 (função personalizada setdiff_sorted) retorna uma lista (comparada a uma matriz na solução 1).

(b) Se você não tem certeza se os elementos são únicos, use a configuração padrão de NumPy setdiff1dnas soluções A e B. O que pode ser um exemplo de complicação? Veja a nota (c).

(c) As coisas serão diferentes se uma das duas listas não for única.
Diga list_2não é único: list2 = ["a", "f", "c", "m", "m"]. Manter list1como está:list_1 = ["a", "b", "c", "d", "e"]
configurando o valor padrão dos assume_uniquerendimentos ["f", "m"](em ambas as soluções). No entanto, se você definir assume_unique=True, as duas soluções dão ["f", "m", "m"]. Por quê? Isso ocorre porque o usuário ASSUME que os elementos são únicos). Por isso, é melhor manterassume_uniqueao seu valor padrão. Observe que as duas respostas estão classificadas.

jcoderepo
fonte
Se suas listas já tiverem sido solicitadas, isso também retornará uma lista solicitada. A solução nativa de converter em conjuntos e obter a diferença (soluções mostradas abaixo) retorna uma lista não ordenada que pode dificultar o exame visual dos resultados.
Doubledown 29/11
1
Olá, @Doubledown! Sua preocupação foi abordada na postagem editada. Espero que isto ajude!
Jcoderepo 19/11/19
182

Você pode usar conjuntos:

main_list = list(set(list_2) - set(list_1))

Resultado:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> set(list_2) - set(list_1)
set(['m', 'f'])
>>> list(set(list_2) - set(list_1))
['m', 'f']

De acordo com o comentário do @JonClements, aqui está uma versão mais organizada:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> list(set(list_2).difference(list_1))
['m', 'f']
nrlakin
fonte
2
Isso é bom se nos preocuparmos apenas com os uniqueelementos, mas e se tivermos vários, m'spor exemplo, isso não seria suficiente.
Chinny84
Isso é verdade. Presumi que o pôster procurava elementos únicos. Suponho que depende do que ele quer dizer com "específico".
Nrlakin
De fato, ps não votei sua resposta negativamente, especialmente para uma pergunta original pouco clara.
Chinny84
13
Você poderia escrever isso como o list(set(list_2).difference(list_1))que evita a setconversão explícita ...
Jon Clements
Não se preocupe! Obrigado @leaf pela assistência de formatação.
Nrlakin
60

Não sei por que as explicações acima são tão complicadas quando você tem métodos nativos disponíveis:

main_list = list(set(list_2)-set(list_1))
A.Kot
fonte
6
Preservar a ordem pode ser a razão
Keith
57

Use uma compreensão de lista como esta:

main_list = [item for item in list_2 if item not in list_1]

Resultado:

>>> list_1 = ["a", "b", "c", "d", "e"]
>>> list_2 = ["a", "f", "c", "m"] 
>>> 
>>> main_list = [item for item in list_2 if item not in list_1]
>>> main_list
['f', 'm']

Editar:

Como mencionado nos comentários abaixo, com grandes listas, o acima não é a solução ideal. Nesse caso, uma opção melhor seria a conversão list_1para uma setprimeira:

set_1 = set(list_1)  # this reduces the lookup time from O(n) to O(1)
main_list = [item for item in list_2 if item not in set_1]
ettanany
fonte
3
Nota: Para maior list_1, você iria querer preconvert a um set/ frozenset, por exemplo set_1 = frozenset(list_1), em seguida main_list = [item for item in list_2 if item not in set_1], reduzindo o tempo de verificação de O(n)por item de (aproximadamente) O(1).
ShadowRanger
@ettanany Cuidado, se você tentar a solução conforme ettanany postou. Tentei a solução da ettanany como está e é realmente super lenta para uma lista maior. Você pode atualizar a resposta para incorporar a sugestão de shadowranger?
Doubledown 11/04/19
Seria possível obter o índice, em vez da string?
JareBear 9/04
@JareBear Você pode usar enumerate()para isso:[index for (index, item) in enumerate(list_2) if item not in list_1]
ettanany
@ ettanany muito obrigado !! Vou implementar o mais cedo possível, eu tinha feito. Mas seu código parece muito mais limpo.
JareBear
5

Se você deseja uma solução de uma linha (ignorando importações) que requer apenas O(max(n, m))trabalho para entradas de comprimento ne m, não O(n * m)trabalho, você pode fazê-lo com o itertoolsmódulo :

from itertools import filterfalse

main_list = list(filterfalse(set(list_1).__contains__, list_2))

Isso tira proveito das funções funcionais que utilizam uma função de retorno de chamada na construção, permitindo que ele crie o retorno de chamada uma vez e reutilize-o para cada elemento sem precisar armazená-lo em algum lugar (porque o filterfalsearmazena internamente); compreensões de lista e expressões geradoras podem fazer isso, mas é feio. †

Isso obtém os mesmos resultados em uma única linha que:

main_list = [x for x in list_2 if x not in list_1]

com a velocidade de:

set_1 = set(list_1)
main_list = [x for x in list_2 if x not in set_1]

Obviamente, se as comparações pretendem ser posicionais, então:

list_1 = [1, 2, 3]
list_2 = [2, 3, 4]

deve produzir:

main_list = [2, 3, 4]

(como o valor in list_2tem uma correspondência no mesmo índice em list_1), você deve definitivamente seguir a resposta de Patrick , que não envolve lists ou s temporários set(mesmo com sets sendo mais ou menos O(1), eles têm um fator "constante" mais alto por verificação do que simples verificações de igualdade ) e envolveO(min(n, m)) trabalho, menos do que qualquer outra resposta e, se o seu problema for sensível à posição, é a única solução correta quando os elementos correspondentes aparecerem em desvios incompatíveis.

†: A maneira de fazer o mesmo com a compreensão de uma lista como uma linha seria abusar do loop aninhado para criar e armazenar em cache valores no loop "mais externo", por exemplo:

main_list = [x for set_1 in (set(list_1),) for x in list_2 if x not in set_1]

que também oferece um benefício de desempenho menor no Python 3 (porque agora set_1é escopo localmente no código de compreensão, em vez de ser pesquisado no escopo aninhado de cada verificação; no Python 2 isso não importa, porque o Python 2 não usa fechamentos para compreensão da lista; eles operam no mesmo escopo em que são usados).

ShadowRanger
fonte
4
main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"]

for i in list_2:
    if i not in list_1:
        main_list.append(i)

print(main_list)

resultado:

['f', 'm']
Inconnu
fonte
Como a solução baseada em compreensão de lista equivalente , isso será lento se list_1for grande e list_2for de tamanho não trivial, pois envolve len(list_2) O(n)varreduras de list_1, tornando-a O(n * m)(onde ne msão os comprimentos de list_2e, list_1respectivamente). Se você converter list_1a um set/ frozensetna frente, o contém cheques pode ser feito em O(1), tornando o trabalho total O(n)da duração do list_2(tecnicamente, O(max(n, m)), desde que você faça O(m)o trabalho para tornar a set).
ShadowRanger
1

Eu faria zipas listas juntas para compará-las elemento a elemento.

main_list = [b for a, b in zip(list1, list2) if a!= b]
Patrick Haugh
fonte
Se o OP quiser comparar elemento por elemento (não está claro, o exemplo pode ser de qualquer maneira), isso é muito mais eficiente do que as outras respostas, pois é uma única passagem barata sobre ambos lists com um único novo listsendo construído, sem temporários adicionais , sem verificações caras de contenção, etc. #
ShadowRanger
1
@ShadowRanger este seria apenas o trabalho para a diferença elemento-wise que é um ponto-chave
prefeito do ford
@fordprefect: Sim. Minha própria resposta cobre diferenças independentes de posição.
ShadowRanger
1

Eu usei dois métodos e achei um método útil sobre outro. Aqui está a minha resposta:

Meus dados de entrada:

crkmod_mpp = ['M13','M18','M19','M24']
testmod_mpp = ['M13','M14','M15','M16','M17','M18','M19','M20','M21','M22','M23','M24']

Método1: np.setdiff1dEu gosto dessa abordagem em detrimento de outras, porque preserva a posição

test= list(np.setdiff1d(testmod_mpp,crkmod_mpp))
print(test)
['M15', 'M16', 'M22', 'M23', 'M20', 'M14', 'M17', 'M21']

Método2: Embora dê a mesma resposta que no Método1, mas perturbe a ordem

test = list(set(testmod_mpp).difference(set(crkmod_mpp)))
print(test)
['POA23', 'POA15', 'POA17', 'POA16', 'POA22', 'POA18', 'POA24', 'POA21']

O Method1 np.setdiff1datende perfeitamente aos meus requisitos. Esta resposta para informações.

Msquare
fonte
0

Se o número de ocorrências deve ser levado em consideração, você provavelmente precisará usar algo como collections.Counter:

list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 
from collections import Counter
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['f', 'm']

Como prometido, isso também pode lidar com diferentes números de ocorrências como "diferença":

list_1=["a", "b", "c", "d", "e", 'a']
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['a', 'f', 'm']
MSeifert
fonte
-1

No ser1 remova os itens presentes no ser2.

Entrada

ser1 = pd.Series ([1, 2, 3, 4, 5]) ser2 = pd.Series ([4, 5, 6, 7, 8])

Solução

ser1 [~ ser1.isin (ser2)]

adnan
fonte
Bem-vindo ao Stack Overflow. Esta pergunta tem oito outras respostas, uma das quais foi aceita pelo pôster original. Descreva como sua resposta melhora o que já foi apresentado.
chb