it-swarm.com.de

Python: Wörterbuch aus der Liste entfernen

Wenn ich eine Liste mit Wörterbüchern habe, sagen Sie:

[{'id': 1, 'name': 'paul'},
{'id': 2, 'name': 'john'}]

und ich möchte das Wörterbuch mit id von 2 (oder name john) entfernen. Was ist der effizienteste Weg, um dies programmgesteuert zu tun (dh ich kenne den Index des Eintrags in der Liste nicht) kann nicht einfach geknackt werden).

44
thelist[:] = [d for d in thelist if d.get('id') != 2]

Edit : da einige Zweifel in einem Kommentar über die Leistung dieses Codes zum Ausdruck gebracht wurden (einige basieren auf einem Missverständnis der Python-Leistungsmerkmale, einige davon, dass über die angegebenen Spezifikationen hinaus angenommen wird, dass sich in der Liste genau ein Diktat mit dem Wert ") befindet 2 für Schlüssel 'id'), möchte ich zu diesem Punkt eine Bestätigung geben.

Auf einer alten Linux-Box, die diesen Code misst:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); thelist[:] = [d for d in thelist if d.get('id') != 2]"
10000 loops, best of 3: 82.3 usec per loop

davon etwa 57 Mikrosekunden für die random.shuffle (erforderlich, um sicherzustellen, dass das zu entfernende Element NICHT IMMER an derselben Stelle ist ;-) und 0,65 Mikrosekunden für die erste Kopie (wer sich über die Auswirkungen der Performance von flachen Kopien von Python-Listen Sorgen macht, ist am meisten natürlich zum Mittagessen ;-), um zu vermeiden, dass die ursprüngliche Liste in der Schleife geändert wird (so dass jedes Bein der Schleife etwas zu löschen hat ;-).

Wenn bekannt ist, dass genau ein Element entfernt werden muss, ist es möglich, es noch schneller zu finden und zu entfernen:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); where=(i for i,d in enumerate(thelist) if d.get('id')==2).next(); del thelist[where]"
10000 loops, best of 3: 72.8 usec per loop

(Verwenden Sie next builtin anstelle der .next-Methode, wenn Sie natürlich Python 2.6 oder besser verwenden.) - Dieser Code bricht zusammen, wenn die Anzahl der Diktate, die die Entfernungsbedingung erfüllen, nicht genau eins ist. Verallgemeinernd haben wir:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]"
10000 loops, best of 3: 23.7 usec per loop

wo das Shuffling entfernt werden kann, da es, wie wir wissen, bereits drei gleichgerichtete Diktate zu entfernen sind. Und der Listcomp geht unverändert gut:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]"
10000 loops, best of 3: 23.8 usec per loop

total nacken und hals, mit nur 3 elementen von 99 zu entfernen. Bei längeren Listen und mehr Wiederholungen gilt dies natürlich noch mehr:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]"
1000 loops, best of 3: 1.11 msec per loop
$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]"
1000 loops, best of 3: 998 usec per loop

Alles in allem lohnt es sich offensichtlich nicht, die Subtilität der Erstellung und Umkehrung der Liste der zu entfernenden Indizes einzusetzen und das vollkommen einfache und offensichtliche Listenverständnis zu nutzen, um möglicherweise 100 Nanosekunden in einem kleinen Fall zu gewinnen - und in einem größeren Fall 113 Mikrosekunden zu verlieren ;-) Das Vermeiden oder Kritik einfacher, unkomplizierter und perfekt leistungsgerechter Lösungen (wie Listenverständnisse für diese allgemeine Klasse der Probleme "einige Elemente aus einer Liste entfernen") ist ein besonders unangenehmes Beispiel für Knuths und Hoares wohlbekannte These, dass "vorzeitige Optimierung" ist die Wurzel allen Übels in der Programmierung "! -)

87
Alex Martelli

Hier ist eine Möglichkeit, dies mit einem Listenverständnis zu tun (vorausgesetzt, Sie nennen Ihre Liste 'foo'):

[x for x in foo if not (2 == x.get('id'))]

Ersetzen Sie 'john' == x.get('name') oder was auch immer angemessen ist.

filter funktioniert auch:

foo.filter(lambda x: x.get('id')!=2, foo)

Und wenn Sie einen Generator wollen, können Sie itertools verwenden:

itertools.ifilter(lambda x: x.get('id')!=2, foo)

Ab Python 3 gibt filter trotzdem einen Iterator zurück, daher ist das Listenverständnis wirklich die beste Wahl, wie Alex vorgeschlagen hat.

Dies ist keine richtige Antwort (da ich glaube, dass Sie bereits einige gute haben), aber ... haben Sie überlegt, ein Wörterbuch mit <id>:<name> anstelle einer Liste von Wörterbüchern zu haben?

7
fortran
# assume ls contains your list
for i in range(len(ls)):
    if ls[i]['id'] == 2:
        del ls[i]
        break

Ist im Durchschnitt wahrscheinlich schneller als die Methoden des Listenverständnisses, da die Liste nicht vollständig durchsucht wird, wenn das betreffende Element früh gefunden wird.

2
Imagist

Sie können folgendes versuchen: 

a = [{'id': 1, 'name': 'paul'},
     {'id': 2, 'name': 'john'}]

for e in range(len(a) - 1, -1, -1):
    if a[e]['id'] == 2:
        a.pop(e)

Wenn Sie nicht von Anfang an aufspringen können, wird die for-Schleife nicht zerstört.

1
zeroDivisible

Angenommen, Ihre Python-Version ist 3.6 oder höher, und Sie benötigen das gelöschte Element nicht. Dies wäre weniger kostspielig ...

Wenn die Wörterbücher in der Liste eindeutig sind:

for i in range(len(dicts)):
    if dicts[i].get('id') == 2:
        del dicts[i]
        break

Wenn Sie alle übereinstimmenden Elemente entfernen möchten:

for i in range(len(dicts)):
    if dicts[i].get('id') == 2:
        del dicts[i]

Sie können dies auch tun, um sicherzustellen, dass das Ermitteln des ID-Schlüssels unabhängig von der Python-Version keinen Keyeror erhöht

if diktiert [i] .get ('id', keine) == 2

0
nixmind

Sie könnten etwas in den folgenden Zeilen versuchen:

def destructively_remove_if(predicate, list):
      for k in xrange(len(list)):
          if predicate(list[k]):
              del list[k]
              break
      return list

  list = [
      { 'id': 1, 'name': 'John' },
      { 'id': 2, 'name': 'Karl' },
      { 'id': 3, 'name': 'Desdemona' } 
  ]

  print "Before:", list
  destructively_remove_if(lambda p: p["id"] == 2, list)
  print "After:", list

Wenn Sie nicht etwas erstellen, das einem Index über Ihre Daten ähnelt, glaube ich nicht, dass Sie es besser machen können, als eine Brute-Force-Tabelle. Wenn Ihre Daten nach dem Schlüssel sortiert sind, den Sie Verwenden, können Sie möglicherweise das Modul bisect verwenden, um Das gewünschte Objekt etwas schneller zu finden.

0
Dirk