2009-08-05 27 views
30

Jeśli mam listy słowników powiedzieć:Python: usunąć słownik z listy

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

i chciałbym, aby usunąć słownik z id 2 (lub imię Jan), co jest najbardziej skutecznym sposobem do tego programowo (to znaczy, nie znam indeksu pozycji na liście, więc nie można go po prostu otworzyć).

Odpowiedz

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

Edit: jak pewne wątpliwości zostały wyrażone w komentarzu na temat wykonywania tego kodu (niektóre oparte na nieporozumienie charakterystyk Pythona, niektóre na założeniu poza podanymi specyfikacjami, że istnieje dokładnie jedna dict w lista o wartości 2 dla klucza "id"), chciałbym zaoferować zabezpieczenie w tym punkcie.

na starej Linuksie, pomiar ten kod:

których
$ 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 

około 57 mikrosekund dla random.shuffle (potrzebne do zapewnienia, że ​​element do usunięcia nie jest zawsze w tym samym miejscu ;-) i 0,65 mikrosekundy dla początkowej kopii (ktokolwiek martwi się o wydajność płytkich kopii list Pythona jest najwyraźniej na lunch ;-), konieczny do uniknięcia zmiany oryginalnej listy w pętli (więc każda noga pętli ma coś do kasować;-).

Kiedy wiadomo, że istnieje dokładnie jeden element, aby usunąć, to jest możliwe, aby zlokalizować i usunąć go jeszcze bardziej sprawny:

$ 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 

(użyj next wbudowane zamiast metody .next jeśli jesteś na Python 2.6 lub lepszy, oczywiście) - ale ten kod ulega rozpadowi, jeśli liczba dykt, które spełniają warunek usunięcia, nie jest dokładnie taka sama. Uogólniając to mamy:

$ 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 

gdzie tasowanie może zostać usunięty ponieważ istnieją już trzy równo rozmieszczone dicts usunąć, jak wiemy. I listcomp niezmienione opłaty również:

$ 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 

całkowicie karku i szyi, a nawet tylko trzy elementy 99 powinny być usunięte. Przy dłuższych list i więcej powtórzeń, to posiada jeszcze przedmiotu:

$ 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 

W sumie, to nie jest oczywiście warto wdrażania subtelności tworzenia i cofania listę indeksów do usunięcia vs listy idealnie proste i oczywiste zrozumienie, aby uzyskać 100 nanosekund w jednym małym przypadku - i stracić 113 mikrosekund w większym ;-). Unikanie lub krytykowanie prostych, prostych i perfekcyjnie dostosowanych do wydajności rozwiązań (jak na przykład rozumienie list dla tej ogólnej klasy "usuwania niektórych pozycji z listy") jest szczególnie przykrym przykładem dobrze znanej tezy Knutha i Hoare'a, że ​​"przedwczesna optymalizacja to korzeniem wszelkiego zła w programowaniu "-)

+1

Dwa powody, dla których jest to złe: kopiuje całą listę i przechodzi przez całą listę, nawet jeśli słownik zawierający id 2 jest pierwszym elementem. – Imagist

+12

@imagist, jest jednak najszybszy - ZROBIĆ to, na miłość boską, nie myśl tylko, że wiesz o czym mówisz, szczególnie. kiedy najwyraźniej nie ;-), * SZCZEGÓLNIE * kiedy przedmiot do usunięcia jest pierwszy (unika się przesuwania każdego innego przedmiotu). I nic nie wskazuje na oryginalne pytanie, że każdy dict na liście MUSI zawsze mieć inną wartość odpowiadającą "id". –

+0

Hmmmm. Nie jest zły. Istnieją dwa podejścia: utworzyć nową listę z niektórymi elementami odfiltrowanymi lub zmodyfikować istniejącą listę, aby usunąć niektóre elementy. To tylko pierwsze podejście. I tak daleko, nie ma nic do powiedzenia, że ​​słownik o id = 2 nie pojawi się więcej niż jeden raz na liście. To lista - nie ma gwarancji wyjątkowości. OP nie sugerował tego ograniczenia. – hughdbrown

0

można spróbować wykonać następujące czynności:

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) 

Jeśli nie można pop od początku - pop od końca, to nie zrujnuje dla pętla.

+0

Masz na myśli "zakres (len (a) - 1, -1, -1)", a nie "zakres (len (a) - 1, 0, -1)". Nie obejmuje to pierwszego elementu listy. Słyszałem, że obecnie odwrotnie jest (odwrotnie). Zobacz mój kod poniżej. – hughdbrown

+0

Oto, do czego dążyłem: >>> a = lista (zakres (5)) >>> a [0, 1, 2, 3, 4] >>> zakres (len (a) - 1, -1, -1) [4, 3, 2, 1, 0] >>> zakres (len (a) - 1, 0, -1) [4, 3, 2, 1] Po prostu poczekaj na komentarz-mangling ... – hughdbrown

4

Oto sposób to zrobić z listowego (zakładając, że nazwa Twojej listy „foo”):

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

zastępczy 'john' == x.get('name') lub cokolwiek odpowiednio.

filter działa również:

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

A jeśli chcesz generator można użyć itertools:

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

Jednak od Python 3, filter powróci iterator anyway , więc zrozumienie listy jest naprawdę najlepszym wyborem, jak zasugerował Alex.

+0

również, .get jest lepszy niż [] tutaj, ponieważ nie pęka, jeśli jakiś dict na liście NIE ma wpisu dla klucza 'id'. –

+0

Dobra sugestia - odpowiednio edytowana odpowiedź. –

0

Można spróbować czegoś wzdłuż następujących linii:

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 

ile można zbudować coś w rodzaju indeksu nad swoimi danymi, ja nie sądzę, że można to zrobić lepiej niż robi brute-force " tabela skanuj "na całej liście. Jeśli twoje dane są sortowane według klucza, którego używasz , możesz użyć modułu bisect na , aby znaleźć obiekt, którego szukasz nieco szybciej.

6

To nie jest właściwie anwser (jak sądzę, masz już ich całkiem dobrych), ale ... czy rozważałeś posiadanie słownika <id>:<name> zamiast listy słowników?

+1

+1: "Jeśli to trudne, robisz to źle." Jeśli chcesz usunąć elementy według atrybutu, użyj słownika z atrybutem. Znacznie prostsze. –

+1

... o ile nie dbasz w ogóle o zachowanie kolejności przedmiotów, nigdy nie chcesz usuwać rzeczy za pomocą innego atrybutu, ciesz się z tego, że nigdy nie zezwalaj na żadne duplikaty dotyczące tego jednego atrybutu, itd. Itd. - zdecydowanie wiele ograniczeń wykraczających poza specyfikację wyrażoną przez PO, aby ta sugestia była uzasadniona ;-). –

+0

Gdybym musiał wziąć wszystkie te specyfikacje za pewnik, powiedziałbym "użyj bazy danych" xD – fortran

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

Prawdopodobnie będzie szybszy niż średnio w metodach rozumienia listy, ponieważ nie przechodzi przez całą listę, jeśli wcześnie znajdzie przedmiot.

+0

wywoła 'KeyError' jeśli dict nie ma' id'. i nie o to prosił OP. – SilentGhost

+0

@Imagist +1 To było dokładnie to, czego szukałem. Uwaga do @SilentGhost: Możesz użyć innego klucza, innego niż "id", jeśli chcesz celować w inną wartość, np .: 'ls [i] ['name'] == 'john':' pasuje i usuń ten słownik. – natureminded

Powiązane problemy