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]

fã-clube
fonte
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].

Rosquinha
fonte
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])
Arkku
fonte
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 .

Moinuddin Quadri
fonte
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))
Daniel Pryden
fonte
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
nonot1
fonte
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    
lbsweek
fonte
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])
Akshay Hazari
fonte
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