Stel dat ik twee lijsten heb, l1
en l2
. Ik wil l1 - l2
uitvoeren, wat alle elementen van l1
retourneert die niet in l2
staan.
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 - l2
moet [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]
l3
zal [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:
-
Arkku’s
set
verschil 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
-
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
-
Moinuddin Quadri’slijstbegrip met op
set
gebaseerde 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
-
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
-
Daniel Pryden’sgenerator-expressie met op
set
gebaseerde lookupen type-casting naarlist
– (583 nsec per lus): Expliciet type-casting naar de lijst om het uiteindelijke object alslist
, zoals gevraagd door OP. Als generatorexpressiewordt vervangen door lijstbegrip, wordt deze hetzelfde als Moinuddin Quadri’slijstbegrip metset
gebaseerde 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
-
Moinuddin Quadri’smet behulp van
filter()
en expliciet typecasten naarlist
(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
-
Akshay Hazari’smet combinatie van
functools.reduce
+filter
-(3.36 usec per lus): Expliciet type-casting naarlist
vanaf Python 3.x begon het terugkerende iterator terug te geven . We moeten ookfunctools
importeren omreduce
te gebruiken in Python 3.xmquadri$ 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:
-
Arkku’s
set
verschil 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
-
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
-
Moinuddin Quadri’slijstbegrip met op
set
gebaseerde 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
-
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
-
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
-
Daniel Pryden’sgenerator-expressie met op
set
gebaseerde lookupen type-casting naarlist
– (0.964 per lus): Expliciet type-casting naar lijst om het uiteindelijke object alslist
, zoals gevraagd door OP. Als generatorexpressiewordt vervangen door lijstbegrip, wordt deze hetzelfde als Moinuddin Quadri’slijstbegrip metset
gebaseerde 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
-
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 set
gegevensstructuur te gebruiken (sinds de in
operator 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 filterfalse
zonderlambda-expressie
Bij gebruik van functies zoals filter
of filterfalse
en soortgelijke van itertools
kun je meestal bespaar prestaties door lambda
-expressies te vermijden en reeds bestaande functies te gebruiken. Instanties van list
en set
definië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 l2
kan 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]
filterfalse
creëert een iterator die alle elementen oplevert die false
teruggeven 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 set
verschilte 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 set
gebaseerde 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 set
internte 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 set
in 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]