list comprehension mit Bedingung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Idefix_1024
User
Beiträge: 19
Registriert: Montag 11. Januar 2010, 14:36

folgendes kann ich nicht verstehen

Code: Alles auswählen

a=[1,2,3,3,3,4,5,5,6,7,8]
b=[a[i] for i in range(0, len(a)) if (a[i] not in b)]
print b
>>> [1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 8]
Ich hätte erwartet, dass lediglich Elemente die noch nicht in b sind aus a übernommen werden. Warum klappt das nicht?
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

also wenn ich das jungfräulich starte bekomme ich
NameError: name 'b' is not defined

Das wirft nur keine Exception wenn b im Vorfeld definiert wurde.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

Der Code kann so nicht funktionieren, ohne `b` vorher irgendwo initialisiert zu haben.

Folgendes funktioniert:

Code: Alles auswählen

>>> a=[1,2,3,3,3,4,5,5,6,7,8]
>>> b = []
>>> b=[a[i] for i in range(0, len(a)) if (a[i] not in b)]
>>> b
[1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 8]
>>> 
Wenn ich dich richtig verstehe, gibt es `b` schon *irgendwo* und enthält auch schon Elemente. Du möchtest jetzt alles aus ´a´ in ´b´ stecken, wenn es in ´b´ noch nicht vorkommt:

Code: Alles auswählen

>>> a=[1,2,3,3,3,4,5,5,6,7,8]
>>> b = [3, 4, 5, 9]
>>> b=[a[i] for i in range(0, len(a)) if (a[i] not in b)]
>>> b
[1, 2, 6, 7, 8]
Das kann mit deiner LC nicht klappen, da ja nur geschaut wird, welche Elemente aus b nicht in a vorkommen. Eine Möglichkeit wäre, die Listen einfach mit einander zu multiplizieren:

Code: Alles auswählen

>>> a=[1,2,3,3,3,4,5,5,6,7,8]
>>> b=[9,10]
>>> b = [i for i in a+b]
>>> b
[1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 8, 9, 10]
Oder es gleich als einfache For-Schleife auszudrücken:

Code: Alles auswählen

>>> for i in a:
...     if i not in b:
...         b += i
... 
>>> b
[1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 8, 9, 10]
PS:

das hier:

Code: Alles auswählen

for i in range(0, len(a))
ist ein Python-Anti-Pattern. Man kann direkt über die Inhalte der Liste iterieren. Man braucht weder range(), noch len(). Alles unnötig.
When we say computer, we mean the electronic computer.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Idefix_1024: die Zuweisung an `b` erfolgt nach dem Erstellen der Liste. Daher ist der Inhalt von `b` der, der vor der Neuzuweisung an `b` gebunden war.

Warum das so nicht funktionieren kann, siehst Du daran dass Du die List-Comprehension ja an gar keine Variable binden mußt:

Code: Alles auswählen

print [a[i] for i in range(0, len(a)) if (a[i] not in ???)]
was soll hier anstelle der ??? stehen?

Dein Problem läßt sich daher nicht mit einer LC lösen, sondern Du brauchst eine normale for-Schleife:

Code: Alles auswählen

b = []
for x in a:
    if x not in b:
        b.append(x)
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

So haben wir mit viel Aufwand eine Menge geschaffen. set ist Dein Freund.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@bwbg: die Frage ist, ob die Reihenfolge relevant ist oder nicht.
Idefix_1024
User
Beiträge: 19
Registriert: Montag 11. Januar 2010, 14:36

Danke für die Hinweise und Entschuldigung, dass ich nicht konkret genug war!

Genau genommen möchte ich aus a[.] alle doppelten Elemente rauswerfen wobei die Elemente in der eigentlichen Anwendung keine Zahlen sondern strings sind. Dazu wollte ich eine neue Liste erstellen in die nur Elemente kommen sollen, die noch nicht dort vorhanden sind.

Ich will es mal ein wenig mehr verdeutlichen. Ich habe eine recht große Menge die ich mit dieser for-loop durcharbeiten müsste. Diese Zeit möchte ich irgendwie reduzieren.
Das Beispiel hier ist ein wenig länger aber dafür vielleicht deutlicher (ja die Reihenfolge ist leider wichtig bei der späteren Verwendung!):

Code: Alles auswählen

import time

a = range(0, 10000)
a.insert(5,5)
a.insert(4,4)
a.insert(3,3)
print("len(a) = "+str(len(a)) + "\n")
b = []
c = []
# solution with for loop
timeStart = time.time()
for x in a:
    if x not in b:
        b.append(x)
print("for-loop elapsed time: " + str(round(time.time()-timeStart, 3)) + " seconds!")
print("first elements of b are: " + str(b[0:10]))
print("len(b) = " + str(len(b)) + "\n")

# solution with list comprehension
timeStart = time.time()
c = [a[i] for i in a if (a[i] not in c)]
print("list comprehension elapsed time: " + str(round(time.time()-timeStart, 3)) + " seconds!")
print("first elements of c are: " + str(c[0:10]))
print("len(c) = " + str(len(c)))
Für den eigentlichen Einsatz muss ich ca. 300000 Elemente durcharbeiten. Das ist mit einer for-loop ein Grauen :-(

set klingt interessant aber wenn die Reihenfolge leidet ist das bei meiner eigentlichen Anwendung ungeeignet. Außer ich kann die Elemente nachher wieder richtig sortieren... aber das ist wahrscheinlich dann auch eher langsam...

list comprehension vergleicht also quasi nur mit meiner leeren c-liste und aktualisiert c nicht während des for Durchlaufs... sowas habe ich fast befürchtet :-(

So exotisch ist das Problem doch eigentlich nicht oder? Gibt es nicht noch einen anderen "schnellen" Weg?
Ich lese mal diese set variante genauer nach...
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Du benutzt offensichtlich Python2 da ist `print` keine Funktion und sollte auch nicht so geschrieben werden, außer in der ersten Zeile steht ein from __future___ import print_function.
Statt + und str solltest Du .format benutzen.
Das was mit `# solution with list comprehension` eingeleitet wird, ist keine Lösung, kann also weg.

Klarer wird mit dem Code Dein Problem aber auch nicht. for-Schleifen sind genau dafür da, etwas sehr oft zu machen. Bei vielen Zahlen ist numpy eine Alternative.
Idefix_1024
User
Beiträge: 19
Registriert: Montag 11. Januar 2010, 14:36

danke!
Ich habe nur leider ein Problem mit der Zeit die eine for Schleife so braucht.

Der Datensatz ist zeilenweise aufgebaut. Jede Zeile (entspricht einem Element in der Liste) enthält einen String, der aus
Datum \t Timestamp \t hex_data_string \t und noch einiges andere besteht.
Ich möchte nun die Zeilen herauslöschen, die das gleiche Datum UND den gleichen Timestamp UND das gleiche hex_data_string enthalten.
Das Ganze ist ja nur eine Vorverarbeitung und sollte nicht ewig lange dauern.
Nachdem ich die Daten alle von einer "Wurzel-Liste" nehme ist auch Multiprocessing nicht wirklich ein Mittel der Wahl oder?

Wie bekomme ich denn so eine for-Schleife schneller? Was ist bei einer List Comprehension denn so viel anders gestrickt, dass die um einen WAHNSINN schneller läuft?
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Die List Comprehension ist schneller, weil sie nicht funktioniert. Du willst ein set benutzen.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Idefix_1024: Bevor man Schleifen und LCs gegenüber stellt, sollte man IMHO erst mal dafür sorgen das die Komplexitätsklasse nicht unnötig gross ist. Also ein `set` statt einer Liste um zu schauen ob man etwas schon einmal gesehen hat. Falls die Reihenfolge keine Rolle spielt, dann kann man ja nur ein `set` verwenden, ansonsten für den ``in``-Test eines anlegen. Ich verwende ja gerne Bibliotheken statt es selbst noch mal zu erfinden, deshalb werfe ich mal `more_itertools.unique_everseen()` in den Raum.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Code: Alles auswählen

c = [a[i] for i in a if (a[i] not in c)]
zur Erklärung der Geschwindigkeit:
erst wird der rechte Teil neben dem = ausgewertet und dann der Variablen c zugewiesen
daher ist "not in c" immer True, da c eine leere Liste ist
es wird daher einfach eine Liste mit allen Elementen aus a erstellt.
Laufzeit O(n)
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Idefix_1024
User
Beiträge: 19
Registriert: Montag 11. Januar 2010, 14:36

ok, es wird wohl nix mit der list comprehension ;-)

ich habe mich mit set angefreundet... nun muss ich meine daten "nur" noch wieder nach dem timestamp sortieren... sonst hilft das Ergebnis auch nix

Der Anfang jedes List-Elements beinhaltet einen Zeitstempel den ich in Sekunden umgerechnet habe. Nach diesen Sekunden muss nun die Liste sortiert werden und dann könnte es wieder was werden!

Code: Alles auswählen

timeStamp = "01:01:01.111"
sum(x * float(t) for x, t in zip([3600, 60, 1], (timeStamp.split(":"))))
dafür gibt es list.sort mit key wenn ich das richtig sehe...

ich denke ihr habt mich in die richtige Richtung geschupst, danke!
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

So langsam wird Dein Problem klarer: Du hast also eine sortierte Liste, bei der manche Zeilen hintereinander doppelt sind. Dann mußt Du auch nur immer mit der Zeile davor vergleichen:

Code: Alles auswählen

previous = None
unique_data = []
for entry in data:
    if entry[:3] != previous: # first three columns are date, timestamp, hex_data
        previous = entry[:3]
        unique_data.append(entry)
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

Standardvorgehen wäre m.E. so etwas (ungetestet):

Code: Alles auswählen

a=[1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 8]
b = []
seen = set()
for x in a:
    if x not in seen:
        b.append(x)
        seen.add(x)
Da kann man dann auch etwas komplizierteres machen, als eine Liste ohne Duplikate.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Idefix_1024:
Ohne einen kleinen Satz an echten Daten (zumindest die echte Formatierung des Datums) ist das hier nur ein Ratespiel und führt zu nichts. Grundsätzlich zu empfehlen ist das Paket dateutil, welches die Handhabung von Datumsangaben etwas vereinfacht. Ich würde in deinem Fall erstmal alles durch ein set() knallen, damit ich die Dubletten los werde. Danach via sorted() mit einem passenden key-Argument die Reihenfolge herstellen. Denkbar wäre hier dateutil.parser.parse(), aber eben je nach Datumsformat, welches wir ja bisher nicht kennen.

Übrigens, wenn dir Geschwindigkeit extrem wichtig ist, dann bist du bei Python wahrscheinlich falsch. Oder du findest ein (mir unbekanntes) Python-Package, welches die Aufgabe (z.B. in C) möglichst schnell erledigt, evtl hat auch das pandas-Paket hierfür ewas Schnelles im Angebot. Oder du schreibst das halt selber außerhalb von Python (entsprechende Kenntnisse vorausgesetzt). BTW: Was macht dich eigentlich so sicher, dass Python zu langsam für deine Datensätze ist...?
Antworten