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?


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.


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

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

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]

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


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

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]

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.


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

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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.