2013-01-16 17 views
6

Was wäre die einfachste und eleganteste Art zu prüfen, ob ein Element in zwei Listen existiert. Zum Beispiel habe ich zwei Listen wie folgt?vergleichen, ob ein Element in zwei Listen existiert

Jetzt in den oben angegebenen Listen möchte ich prüfen, ob es in beiden Listen ein Element gibt. Derzeit mache ich es wie folgt

def elm_exists(list_a, list_b): 
    for elm in list_a: 
     if elm in list_b: 
      return True 
    return False 

gibt es eine elegantere Möglichkeit, dies zu tun?

+2

Sind die Elemente innerhalb jeder Liste einzigartig? Wenn ja, können Sie das ganz einfach mit 'Sets' machen. –

+0

Ja, die Elemente in einer Liste sind eindeutig. – Amyth

+1

@Mike: Warte ... warum konntest du das nicht mit Sets machen, auch wenn die Elemente _weren't_ einmalig sind? Sie verlieren die Information, dass ein Element mehrere Male existiert, aber wenn Sie nur darauf achten, dass das Element existiert, haben Sie diese Information nicht benötigt. (Und wenn du es tust, könntest du immer einen 'Counter' anstelle eines' Sets' verwenden, um es zu behalten.) – abarnert

Antwort

8

Kontrollelemente von a und b mit diesem:

set(a).intersection(b) 

Beispiel:

In [44]: nk=set(a).intersection(b) 

In [45]: for x in a: 
    ...:  if x in nk: 
    ...:   print x, 'present in b' 
    ...:  else: 
    ...:   print x, 'absent in b' 
    ...:   
a absent in b 
b absent in b 
g present in b 
r absent in b 

auch:

In [47]: timeit(set(a) & set(b)) 
1000000 loops, best of 3: 940 ns per loop 

In [48]: timeit(set(a).intersection(b)) 
1000000 loops, best of 3: 854 ns per loop 

In [49]: timeit([x for x in a if x in b]) 
1000000 loops, best of 3: 1 us per loop 

daher set(a).intersection(b) verwenden.

+0

@ JonClements, Danke, sehr elegant! – Amyth

+0

Ich bin mir nicht sicher, ob es viel Sinn macht, die Ergebnisse mit so kurzen Listen zu vergleichen. Wenn Sie nur die Längen auf 3 und 4 statt auf 4 und 5 reduzieren, reicht das aus, um "Listen" -Vergleiche schneller zu machen als die "set" -Versionen, zumindest auf meinem Computer, aber ich glaube nicht, dass dies tatsächlich bedeutet, ein '' zu machen O (NM) 'Algorithmus ist besser als und' O (N + M) 'eins! – abarnert

+1

Siehe meine bearbeitete Antwort. Es hängt von der Größe Ihres Datensatzes ab, _und_ der Häufigkeit von Kollisionen. Was hätte offensichtlich sein sollen ... aber erst, als ich versuchte, die Zahlen zu verstehen. Wie auch immer, in den meisten Fällen sind beide Versionen und mein Generator-Ausdruck über 'set (a)' mehr als schnell genug, und die ursprüngliche Frage des OPs war eher Eleganz als Leistung, also ... Ich denke, "Schnittpunkt" ist eleganter und lesbarer als '&', und der 'any ... in ... for' Ausdruck noch mehr, aber YMMV. – abarnert

6

Dies funktioniert:

>>> l1,l2=['a', 'b', 'g', 'r'], ['e', 'g', 'l', 1, 'w'] 
>>> list(set(l1)&set(l2)) 
['g'] 
1
def elm_exists(lista, listb): 
    return bool(set(lista) & set(listb)) 

Die bool Funktion True für alle nicht-leeren Behälter zurück, und False für leere. Der gesetzte Schnittpunkt (&) gibt eine Menge der gemeinsamen Elemente der beiden Sätze zurück. Beachten Sie, dass Sets alle Duplikate entfernen.

Alternativ können Sie set(lista).intersection(listb) in der bool Funktion verwenden.

+0

das 'set (listb)' innerhalb der Schnittmenge ist redundant, da '.intersection' jedes iterable (s) als Argument nimmt –

+0

Auch brauchen Sie 'len' hier nicht, da' bool (len (x)) '' ist garantiert das gleiche wie 'bool (x)', beides einfacher und effizienter. – abarnert

+0

Danke JonClements und abarnert, Änderungen wurden vorgenommen – Volatility

0
>>> y = [1,23,3] 
>>> z = [3,432] 
>>> (3 in y) and (3 in z) 
True 
3

Sie müssen nicht beide list s set s konvertieren, nur eine. Ich denke, das Überspringen der unnötigen Umwandlung macht es lesbarer und eleganter.

Also, entweder:

set(a).intersection(b) 

Oder:

s = set(a) 
any(e in s for e in b) 

Letzteres hat den Vorteil, kurzzuschließen, sobald es eine Übereinstimmung findet, und besser die Logik zum Ausdruck, und die Rückkehr True oder False statt einer nicht-falsch oder falsch set, aber es ist zwei Zeilen statt einer, wenn das stört Sie. Ich habe keine Ahnung, ob dieser Vorteil die Kosten für das Einfügen der Schleife in einen Generatorausdruck anstatt in eine C-Funktion ausgleicht.

Performance-Ergebnisse mit list s diese klein sind fast bedeutungslos, so lassen Sie uns dies versuchen:

In [373]: a=[random.choice(string.ascii_lowercase) for _ in range(10000)] 
In [374]: b=[random.choice(string.ascii_lowercase) for _ in range(10000)] 
In [375]: %timeit(set(a)) 
10000 loops, best of 3: 180 us per loop 
In [376]: s=set(a) # since all versions need to do this 
In [391]: %timeit(s & set(b)) 
1000000 loops, best of 3: 178 us per loop 
In [392]: %timeit(s.intersection(b)) 
1000000 loops, best of 3: 247 us per loop 
In [393]: %timeit(discard(e in s for e in b)) 
1000000 loops, best of 3: 550 ns per loop 
In [394]: %timeit(any(e in s for e in b)) 
1000000 loops, best of 3: 749 ns per loop 
In [395]: %timeit(any(e in a for e in b)) 
1000000 loops, best of 3: 1.42 us per loop 

die Zahlen Um alle im Nanosekundenskala zu setzen, in den Kosten der set(a), dass alle, aber die letzten wieder hinzufügen benötigen, und vergleichen Sie die gleichen Tests von drei Python-Versionen (Apple-Aktie CPython 2.7.2, Python.org CPython 3.3.0, Homebrew PyPy 1.9.0/2.7.2, alle 64-Bit-Mac-Builds):

Nun, da ich darüber nachdenke, macht dies insgesamt Sinn. Die Chancen auf eine Kollision sind sehr hoch, so dass die Kosten für die Umwandlung des Ganzen in ein Set alles dominieren.

Das bedeutet, wir brauchen einen neuen Test mit 10000 eindeutigen Werten. Lassen Sie uns den Test mit diesem wiederholen:

In [29]: a, b = list(range(10000)), list(range(10000)) 
In [30]: random.shuffle(a) 
In [31]: random.shuffle(b) 

        CP272 CP330 PyPy 
s & set(b)  1277000 1168000 1141000 
s.intersection(b) 1165000 1117000 2520000 
discard(genexp) 1699000 1271000 770000 
any(genexp)  389800 344543 320807 
any(list-genexp) 62000 10400 1520 

Diese sind vernünftiger. Und sie machen immer noch Sinn. Wenn Sie dieselben 10000 Elemente zufällig mischen, wie weit müssen Sie jeweils gehen? Nicht weit genug, um die Kosten von set zu verdienen - eine der Listen lohnend zu machen, viel weniger beide!

Also, lassen Sie uns einen Fall versuchen, wo es keine Spiele:

In [43]: a=list(range(10000, 20000)) 


        CP272  CP330  PyPy 
s & set(b)   751000 770000 733000 
s.intersection(b) 466000 530000 1920000 
discard(genexp)  1246000 985000 749000 
any(genexp)   1269000 966000 893000 
any(list-genexp) 185000000 176000000 5870000 

Ich habe keine Ahnung, wie PyPy das letzte tat so schnell, aber anders als das, hier keine Überraschungen.

Also, welches ist das beste?

Wenn Sie viele Kollisionen erwarten, sollten Sie es vermeiden, die Sätze wann immer möglich zu erstellen. Wenn Sie jedoch nur wenige Kollisionen erwarten, möchten Sie mindestens einen Satz erstellen. Wenn Sie keine Ahnung haben, denke ich, dass die sicherste Wette any(genexp) ist - im schlimmsten Fall ist sie weniger als 3x so schlecht wie die beste, und wenn es eine Chance gibt, dass die Kollisionsrate hoch ist, wird sie viel schneller sein. Aber Sie können sich die Zahlen ansehen und selbst sehen.

Oder, besser natürlich, Zeit sie alle gegen echte Testdaten, die Sie erwarten zu begegnen.

+0

Gute Arbeit (und +1), aber das OP fragte nach dem "einfachsten" und "elegantesten", nicht am schnellsten! Als ein Numpy-Benutzer ist die erste Funktion, die mir vorkommt, 'np.any (np.in1d ​​(x, y))'. Ich wäre gespannt, wie das aussieht. Ihre Beweise unterstützen jedoch eine allgemeine Regel ... Die Kosten für die Konvertierung der Daten übersteigen normalerweise die Kosten des Tests, außer wenn der Datensatz sehr groß ist. – cxrodgers

+0

@cxrodgers: Am Anfang meiner Antwort steht "lesbar" und "elegant", und das war alles, was es in der ursprünglichen Version gab, und ich denke, das ist immer noch der wichtigste Teil der Antwort. All die anderen Sachen wurden später hinzugefügt, nach den unvermeidlichen Argumenten darüber, was am schnellsten ist; Der einzige Grund, warum es so viel länger ist als der wichtige Teil ist, dass Sie Einfachheit einfach halten können, aber Sie können Leistungstests nicht einfach halten. – abarnert

0

Dies ist eine andere Lösung,

>>> c = [filter(lambda x: x in b, sublist) for sublist in a] 
>>> filter (lambda a: a != '', c) 
['g'] 
Verwandte Themen