Verwijder alle elementen die in de ene lijst voorkomen uit een andere

Stel dat ik twee lijsten heb, l1en l2. Ik wil l1 - l2uitvoeren, wat alle elementen van l1retourneert die niet in l2staan.

Ik kan een naïeve lusbenadering bedenken om dit te doen, maar dat zal echt inefficiënt zijn. Wat is een pythonische en efficiënte manier om dit te doen?

Als ik bijvoorbeeld l1 = [1,2,6,8] and l2 = [2,3,5,8]heb, l1 - l2moet [1,6]

teruggeven


Antwoord 1, autoriteit 100%

Python heeft een taalfunctie genaamd List Comprehensionsdie perfect geschikt is om dit soort dingen extreem gemakkelijk te maken. Het volgende statement doet precies wat je wilt en slaat het resultaat op in l3:

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

l3zal [1, 6]bevatten.


Antwoord 2, autoriteit 32%

Een manier is om sets te gebruiken:

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

Houd er echter rekening mee dat sets de volgorde van elementen niet behouden en ervoor zorgen dat dubbele elementen worden verwijderd. De elementen moeten ook hashbaar zijn. Als deze beperkingen acceptabel zijn, is dit vaak de eenvoudigste en best presterende optie.


Antwoord 3, autoriteit 16%

Prestatievergelijkingen

De prestaties vergelijken van alle antwoorden die hier worden genoemd op Python 3.9.1en Python 2.7.16.

Python 3.9.1

Antwoorden worden vermeld in volgorde van uitvoering:

  1. Arkku’ssetverschil met behulp van aftrekken“-” bewerking – (91,3 nsec per lus)

    mquadri$ python3 -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    5000000 loops, best of 5: 91.3 nsec per loop
    
  2. Moinuddin Quadri’smet behulp van set().difference()– (133 nsec per lus)

    mquadri$ python3 -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1.difference(l2)"
    2000000 loops, best of 5: 133 nsec per loop
    
  3. Moinuddin Quadri’slijstbegrip met op setgebaseerde opzoeken– (366 nsec per lus)

    mquadri$ python3 -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 5: 366 nsec per loop
    
  4. Donut’sbegrip van de lijst op gewone lijst– (489 nsec per lus)

    mquadri$ python3 -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
     500000 loops, best of 5: 489 nsec per loop
    
  5. Daniel Pryden’sgenerator-expressie met op setgebaseerde lookupen type-casting naar list(583 nsec per lus): Expliciet type-casting naar de lijst om het uiteindelijke object als list, zoals gevraagd door OP. Als generatorexpressiewordt vervangen door lijstbegrip, wordt deze hetzelfde als Moinuddin Quadri’slijstbegrip met setgebaseerde lookup.

    mquadri$ mquadri$ python3 -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(x for x in l1 if x not in l2)"
     500000 loops, best of 5: 583 nsec per loop
    
  6. Moinuddin Quadri’smet behulp van filter()en expliciet typecasten naar list(moet expliciet typecasten zoals in Python 3.x, het retourneert iterator) – (681 nsec per lus)

    mquadri$ python3 -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(filter(lambda x: x not in l2, l1))"
     500000 loops, best of 5: 681 nsec per loop
    
  7. Akshay Hazari’smet combinatie van functools.reduce+ filter-(3.36 usec per lus): Expliciet type-casting naar listvanaf Python 3.x begon het terugkerende iterator terug te geven . We moeten ook functoolsimporteren om reducete gebruiken in Python 3.x

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

Python 2.7.16

Antwoorden worden vermeld in volgorde van uitvoering:

  1. Arkku’ssetverschil met behulp van aftrekken“-” bewerking – (0,0783 usec per lus)

    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.0783 usec per loop
    
  2. Moinuddin Quadri’smet behulp van set().difference()– (0.117 usec per lus)

    mquadri$ mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1.difference(l2)"
    10000000 loops, best of 3: 0.117 usec per loop
    
  3. Moinuddin Quadri’slijstbegrip met op setgebaseerde opzoeken– (0,246 usec per lus)

    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.246 usec per loop
    
  4. Donut’sbegrip van de lijst op gewone lijst– (0.372 usec per lus)

    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.372 usec per loop
    
  5. Moinuddin Quadri’smet behulp van filter()– (0.593 usec per lus)

    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.593 usec per loop
    
  6. Daniel Pryden’sgenerator-expressie met op setgebaseerde lookupen type-casting naar list(0.964 per lus): Expliciet type-casting naar lijst om het uiteindelijke object als list, zoals gevraagd door OP. Als generatorexpressiewordt vervangen door lijstbegrip, wordt deze hetzelfde als Moinuddin Quadri’slijstbegrip met setgebaseerde lookup.

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(x for x in l1 if x not in l2)"
     1000000 loops, best of 3: 0.964 usec per loop
    
  7. Akshay Hazari’smet combinatie van functools.reduce+ filter-(2,78 usec per lus)

    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: 2.78 usec per loop
    

Antwoord 4, autoriteit 5%

Voortbordurend op het antwoord van Donut en de andere antwoorden hier, kunt u nog betere resultaten krijgen door een generatorbegrip te gebruiken in plaats van een lijstbegrip, en door een setgegevensstructuur te gebruiken (sinds de inoperator is O(n) op een lijst maar O(1) op een set).

Dus hier is een functie die voor u zou werken:

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

Het resultaat is een iterable die lui de gefilterde lijst ophaalt. Als je een echt lijstobject nodig hebt (bijvoorbeeld als je een len()op het resultaat moet doen), dan kun je eenvoudig een lijst maken zoals:

filtered_list = list(filter_list(full_list, excludes))

Antwoord 5, autoriteit 5%

Gebruik het settype Python. Dat zou het meest Pythonic zijn. 🙂

Omdat het native is, zou het ook de meest geoptimaliseerde methode moeten zijn.

Zie:

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

http://docs.python.org/library/sets.htm( voor oudere python)

# 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

Antwoord 6, autoriteit 2%

gebruik Begrippen instellen{x voor x in l2} of stel (l2) om in te stellen, gebruik vervolgens List Comprehensionsom lijst

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

benchmark-testcode:

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

resultaat benchmarktest:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    

Antwoord 7

Alternatieve oplossing:

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

Antwoord 8

Gebruik filterfalsezonderlambda-expressie

Bij gebruik van functies zoals filterof filterfalseen soortgelijke van itertoolskun je meestal bespaar prestaties door lambda-expressies te vermijden en reeds bestaande functies te gebruiken. Instanties van listen setdefiniëren een __contains__-methode om te gebruiken voor inperkingscontroles. De in-operator roept deze methode onder de motorkap aan, dus het gebruik van x in l2kan worden vervangen door l2.__contains__(x). Meestal is deze vervanging niet echt mooier, maar in dit specifieke geval kunnen we betere prestaties behalen dan het gebruik van een lambda-expressie, wanneer gebruikt in combinatie met filterfalse:

>>> from itertools import filterfalse
>>> l1 = [1, 2, 6, 8]
>>> l2 = [2, 3, 5, 8]
>>> list(filterfalse(l2.__contains__, l1))
[1, 6]

filterfalsecreëert een iterator die alle elementen oplevert die falseteruggeven wanneer gebruikt als argument voor l2.__contains__.

Sets heeft een snellere implementatie van __contains__dus nog beter is:

>>> from itertools import filterfalse
>>> l1 = [1, 2, 6, 8]
>>> l2 = set([2, 3, 5, 8])
>>> list(filterfalse(l2.__contains__, l1))
[1, 6]

Prestaties

Lijst gebruiken:

$  python3 -m timeit -s "from itertools import filterfalse; l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(filterfalse(l2.__contains__, l1))"
500000 loops, best of 5: 522 nsec per loop

Set gebruiken:

$ python3 -m timeit -s "from itertools import filterfalse; l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(filterfalse(l2.__contains__, l1))"
1000000 loops, best of 5: 359 nsec per loop

Antwoord 9

Sets versus lijstbegrip benchmark op Python 3.8

(toevoegen aan de benchmarks van Moinuddin Quadri)

tldr: Gebruik Arkku’s vaste oplossing, het is zelfs sneller dan beloofd in vergelijking!

Bestaande bestanden controleren aan de hand van een lijst

In mijn voorbeeld vond ik het 40 keer (!)sneller om Arkku’s set-oplossingte gebruiken dan het pythonische lijstbegripvoor een toepassing in de echte wereld om bestaande bestandsnamen te vergelijken met een lijst.

Lijst begrip:

%%time
import glob
existing = [int(os.path.basename(x).split(".")[0]) for x in glob.glob("*.txt")]
wanted = list(range(1, 100000))
[i for i in wanted if i not in existing]

Wandtijd: 28,2 s

Sets

%%time
import glob
existing = [int(os.path.basename(x).split(".")[0]) for x in glob.glob("*.txt")]
wanted = list(range(1, 100000))
set(wanted) - set(existing)

Wandtijd: 689 ms


Antwoord 10

Probeer dit:

l1=[1,2,6,8]
l2=[2,3,5,8]
r=[]
for x in l1:
    if x in l2:
        continue
    r=r+[x]
print(r)

Antwoord 11

Met set.difference():

Je kunt set.difference()om een ​​nieuwe set te krijgen met elementen in de set die niet in de andere zitten. d.w.z. set(A).difference(B)retourneert een set met items die aanwezig zijn in A, maar niet in B. Bijvoorbeeld:

>>> set([1,2,6,8]).difference([2,3,5,8])
{1, 6}

Het is een functionele benadering om setverschilte krijgen dat wordt genoemd in Arkku’s antwoord(die rekenkundige aftrekking -operator gebruikt voor setverschil).

Aangezien setsniet besteld zijn, verliest u de bestelling van elementen uit de eerste lijst. (lees het volgende gedeelte verder als u de volgorde van de elementen wilt behouden)

Lijstbegripgebruiken met op setgebaseerde zoekopdrachten

Als u de volgorde van de oorspronkelijke lijst wilt behouden, dan Donut’s lijstbegripgebaseerd antwoord zal het lukken. U kunt echter beter presterenvan het geaccepteerde antwoord door setinternte gebruiken om te controleren of het element in een andere lijst aanwezig is. Bijvoorbeeld:

l1, l2 = [1,2,6,8], [2,3,5,8]
s2 = set(l2)  # Type-cast `l2` to `set`
l3 = [x for x in l1 if x not in s2]
                             #   ^ Doing membership checking on `set` s2

Als je wilt weten waarom lidmaatschapscontrole sneller is setin vergelijking met list, lees dan dit: Wat maakt sets sneller dan lijsten?


Gebruik filter()en lambda-expressie

Hier is nog een alternatief met filter()met de lambda-uitdrukking. Ik voeg het hier alleen ter referentie toe, maar het is niet prestatie-efficiënt:

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

Other episodes