Remova todos os elementos que ocorrem em uma lista da outra


367

Digamos que eu tenho duas listas l1e l2. Eu quero executar l1 - l2, que retorna todos os elementos de l1não dentro l2.

Posso pensar em uma abordagem de loop ingênuo para fazer isso, mas isso será realmente ineficiente. O que é uma maneira pitônica e eficiente de fazer isso?

Como exemplo, se eu tiver l1 = [1,2,6,8] and l2 = [2,3,5,8], l1 - l2deve retornar[1,6]


12
Apenas uma dica: PEP8 afirma que minúscula "L" não deve ser usado porque ele se parece muito com um 1.
spelchekr

2
Concordo. Li toda a pergunta e as respostas, perguntando-me por que as pessoas continuavam usando onze e doze. Foi só quando li o comentário do @spelchekr que isso fez sentido.
robline


@JimG. Dataframe e lista não são a mesma coisa.
reduzindo a atividade

Respostas:


492

O Python possui um recurso de linguagem chamado List Comprehensions, perfeitamente adequado para tornar esse tipo de coisa extremamente fácil. A declaração a seguir faz exatamente o que você deseja e armazena o resultado l3:

l3 = [x for x in l1 if x not in l2]

l3irá conter [1, 6].


8
Muito pitônico; Eu gosto disso! Qual é a eficiência?
Fandom

2
Eu acredito que seja bastante eficiente e tem o benefício de ser extremamente legível e claro quanto ao que você está tentando realizar. Me deparei com um post de blog que você pode achar interessante em relação à eficiência: blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
Doughnut

6
@ random: a compreensão da lista em si é bastante eficiente (embora a compreensão do gerador possa ser mais eficiente, não duplicando elementos na memória), mas o inoperador não é tão eficiente em uma lista. inem uma lista é O (n), enquanto inem um conjunto é O (1). No entanto, até chegar a milhares de elementos ou mais, é improvável que você note a diferença.
Daniel Pryden

11
l3 = [x for x in l1 if x not in set(l2)]? Estou certo de que set(l2)seria chamado mais de uma vez.
Danosaure

5
Você também pode simplesmente definir l2s = set(l2)e depois dizer l3 = [x for x in l1 if x not in l2s]. Um pouco mais fácil.
Spelchekr

149

Uma maneira é usar conjuntos:

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])

58
Isso também removerá duplicatas l1, o que pode ser um efeito colateral indesejado.
Kindall

37
..e perder a ordem do elemento (se a ordem for importante).
Danosaure

3
Eu só quero acrescentar que eu cronometrado desta vs. a resposta aceita e era mais alto desempenho por um fator de cerca de 3: timeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985 timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.04106225999998969. Então, se o desempenho é um fator significativo, esta resposta pode ser mais apropriado (e também se você não se preocupam com duplicatas ou ordem)
wfgeo

38

Como alternativa, você também pode usar filtercom a expressão lambda para obter o resultado desejado. Por exemplo:

>>> l1 = [1,2,6,8]
>>> l2 = set([2,3,5,8])

#     v  `filter` returns the a iterator object. Here I'm type-casting 
#     v  it to `list` in order to display the resultant value
>>> list(filter(lambda x: x not in l2, l1))
[1, 6]

Comparação de desempenho

Aqui estou comparando o desempenho de todas as respostas mencionadas aqui. Como esperado, a set operação baseada em Arkku é mais rápida.

  • Diferença de conjunto de Arkku - primeiro (0,124 usec por loop)

    mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    10000000 loops, best of 3: 0.124 usec per loop
    
  • Compreensão da lista de Daniel Pryden com setpesquisa - segundo (0,302 usec por loop)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.302 usec per loop
    
  • Compreensão da lista de rosquinhas na lista simples - Terceira (0,552 usec por loop)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.552 usec per loop
    
  • O uso defilter Moinuddin Quadri - Quarto (0,972 usec por loop)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "filter(lambda x: x not in l2, l1)"
    1000000 loops, best of 3: 0.972 usec per loop
    
  • Akshay Hazari usando a combinação de reduce+filter - Quinto (3,97 usec por loop)

    mquadri$ python -m timeit "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "reduce(lambda x,y : filter(lambda z: z!=y,x) ,l1,l2)"
    100000 loops, best of 3: 3.97 usec per loop
    

PS: set não mantém a ordem e remove os elementos duplicados da lista. Portanto, não use diferença definida se precisar de alguma dessas opções .


32

Expandindo a resposta de Donut e as outras respostas aqui, você pode obter resultados ainda melhores usando uma compreensão de gerador em vez de uma compreensão de lista e usando uma setestrutura de dados (já que o inoperador é O (n) em uma lista, mas O (1) em um conjunto).

Então, aqui está uma função que funcionaria para você:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

O resultado será um iterável que buscará preguiçosamente a lista filtrada. Se você precisar de um objeto de lista real (por exemplo, se precisar fazer uma avaliação len()do resultado), poderá criar facilmente uma lista como esta:

filtered_list = list(filter_list(full_list, excludes))

29

Use o tipo de conjunto Python. Isso seria o mais pitonico. :)

Além disso, como é nativo, também deve ser o método mais otimizado.

Vejo:

http://docs.python.org/library/stdtypes.html#set

http://docs.python.org/library/sets.htm (para python mais antigo)

# Using Python 2.7 set literal format.
# Otherwise, use: l1 = set([1,2,6,8])
#
l1 = {1,2,6,8}
l2 = {2,3,5,8}
l3 = l1 - l2

5
Ao usar conjuntos, observe que a saída de é ordenada, ou seja, {1,3,2} se torna {1,2,3} e {"A", "C", "B"} se torna {"A", "B", "C"} e talvez você não queira ter isso.
Pablo Reyes

2
esse método não funcionará se a lista l1incluir elementos repetidos.
Jdhao # 8/18

10

use Set Comprehensions {x para x em l2} ou set (l2) para definir, depois use List Comprehensions para obter list

l2set = set(l2)
l3 = [x for x in l1 if x not in l2set]

código de teste de referência:

import time

l1 = list(range(1000*10 * 3))
l2 = list(range(1000*10 * 2))

l2set = {x for x in l2}

tic = time.time()
l3 = [x for x in l1 if x not in l2set]
toc = time.time()
diffset = toc-tic
print(diffset)

tic = time.time()
l3 = [x for x in l1 if x not in l2]
toc = time.time()
difflist = toc-tic
print(difflist)

print("speedup %fx"%(difflist/diffset))

resultado do teste de benchmark:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    

11
l2set = set( l2 )em vez del2set = { x for x in l2 }
cz

11
Soultion agradável! Mas é preciso ter em mente que ele só funciona com objetos laváveis.
Eerik Sven Puudist

7

Solução alternativa:

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])

2
Existe alguma vantagem em usar esse método? Parece que é mais complexo e mais difícil de ler, sem muitos benefícios.
Skrrgwasme 7/11

Isso pode parecer complexo. Reduzir é muito flexível e pode ser usado para várias finalidades. É conhecido como dobra. reduzir é realmente foldl. Suponha que você queira adicionar coisas mais complexas, então será possível nessa função, mas a compreensão da lista, que é a melhor resposta selecionada, fornecerá apenas uma saída do mesmo tipo, ou seja, lista e provavelmente do mesmo comprimento, com dobras que você poderia altere o tipo de saída também. pt.wikipedia.org/wiki/Fold_%28higher-order_function%29 . Esta solução tem n * m ou menos complexidade. Outros podem ou não ser melhores.
Akshay Hazari

11
reduzir (função, lista, acumulador inicial (que pode ser de qualquer tipo))
Akshay Hazari
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.