2009-08-05 2 views
30

Если у меня есть список словарей, скажет:Python: удалить словарь из списка

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

, и я хотел бы удалить словарь с id 2 (или именем джон), что является наиболее эффективным способом проделать это программно (то есть, я не знаю индекс записи в списке, поэтому его нельзя просто выскочить).

ответ

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

Edit: некоторые сомнения были высказаны в комментарии о выполнении этого кода (некоторые на основе непонимания характеристик Python, некоторые на предположении, за данной спецификации, что существует ровно один ДИКТ в список со значением 2 для ключа «id»), я хочу предложить заверения в этом вопросе.

На старой коробке Linux, измеряя этот код:

$ 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 

из которых около 57 микросекунд для random.shuffle (необходимо для того, чтобы элемент для удаления не всегда на том же месте ;-) и 0,65 микросекунды для первоначальной копии (кто бы ни беспокоился о влиянии производительности мелких копий списков Python, наиболее очевидно, чтобы обедать ;-), чтобы избежать изменения исходного списка в цикле (так что каждая нога цикла имеет что-то Удалить;-).

Когда известно, что существует ровно один элемент для удаления, можно обнаружить и удалить его еще более оперативно:

$ 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 

(используйте next встроенный, а не метод .next, если вы на Python 2.6 или лучше, конечно), но этот код ломается, если число dicts, удовлетворяющих условию удаления, не является точно одним. Обобщая это, мы имеем:

$ 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 

где перетасовки могут быть удалены, потому что есть уже три эквидистантно dicts, чтобы удалить, как мы знаем. И listcomp, без изменений, тарифы также:

$ 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 

полностью вровень с даже только 3 элементы 99, которые будут удалены. С более длинными списками и более повторений, это имеет место даже более естественно:

$ 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 

В общем, это, очевидно, не стоит развернуть тонкость решений и обратить вспять список индексов, чтобы удалить, по сравнению с совершенно простой и очевидный список понимание, чтобы, возможно, получить 100 наносекунд в одном маленьком случае - и потерять 113 микросекунд в более крупном ;-). Избежать или критиковать простые, простые и идеально подходящие для исполнения решения (например, понимание списков для этого общего класса «удалить некоторые предметы из списка») - особенно неприятный пример известного тезиса Кнута и Хора, что «преждевременная оптимизация корень всех зол в программировании "-)

+1

Две причины, почему это плохо: он копирует весь список, и он проходит весь список, даже если словарь, содержащий id 2, является самым первым элементом. – Imagist

+12

@imagist, это тем не менее быстро - ИЗМЕРИТЕ его, ради всего святого, не просто ПРИНИМАЙТЕ, что вы знаете, о чем говорите, особенно. когда вы, очевидно, не делаете :-), * ESPECIALLY *, когда элемент для удаления является первым (он избегает перемещения каждого другого элемента). И в исходном вопросе нет указаний на то, что каждый dict в списке ДОЛЖЕН всегда иметь другое значение, соответствующее «id». –

+0

Хмммм. Неплохо. Существует два подхода: создать новый список с некоторыми элементами, отфильтрованными или изменить существующий список, чтобы удалить некоторые элементы. Это только прежний подход. И что касается этого, нечего сказать, что словарь с id = 2 не будет отображаться более одного раза в списке. Это список - нет гарантии уникальности. И ОП не предлагал этого ограничения. – hughdbrown

0

Вы можете попробовать следующее:

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) 

Если Вы не можете выскочить с самого начала - поп с конца, это не испортит для петля.

+0

Вы имеете в виду «диапазон (len (a) - 1, -1, -1)», а не «диапазон (len (a) - 1, 0, -1)». Это не включает первый элемент списка. Я слышал слово, что reverseed() теперь предпочтительнее. См. Мой код ниже. – hughdbrown

+0

Вот что я получаю по адресу: >>> a = список (диапазон (5)) >>> a [0, 1, 2, 3, 4] >>> range (len (a) - 1, -1, -1) [4, 3, 2, 1, 0] >>> диапазон (len (a) - 1, 0, -1) [4, 3, 2, 1] Просто дождитесь комментирования комментариев ... – hughdbrown

4

Вот способ сделать это с пониманием списка (если вы называете «Foo» вашего списка):

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

замещающий 'john' == x.get('name') или что-то в зависимости от обстоятельств.

filter также работает:

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

И если вы хотите, генератор можно использовать itertools:

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

Однако, как в Python 3, filter возвращает итератор в любом случае , поэтому понимание списка действительно лучший выбор, как предложил Алекс.

+0

также, .get лучше, чем [] здесь, так как он не сломается, если какой-либо dict в списке НЕ имеет записи для ключа 'id'. –

+0

Хорошее предложение - отредактированный ответ соответственно. –

0

Вы могли бы попробовать что-то по следующим направлениям:

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 

Если вы не построить что-то похожее на индекс по вашим данным, я не думаю, что вы можете сделать лучше, чем делать грубую силу " таблица сканирование "по всему списку. Если ваши данные отсортированы по ключевому слову, который вы используете , возможно, вы можете использовать модуль bisect bisect, чтобы найти объект, который вы ищете несколько быстрее.

6

Это не правильно anwser (как я думаю, у вас уже есть некоторые хорошие из них), но ... вы считаете, что у вас есть словарь <id>:<name> вместо списка словарей?

+1

+1: «Если это сложно, вы делаете это неправильно». Если вы хотите удалить вещи по атрибуту, используйте словарь, связанный с атрибутом. Гораздо проще. –

+1

... до тех пор, пока вы вообще не заботитесь о сохранении порядка предметов, никогда не хотите удалять вещи по другому атрибуту, довольны тем, что никогда не допускаете дубликатов в отношении этого одного атрибута и т. Д. И т. Д. многие ограничения выше и выше любых спецификаций, выраженных ОП, чтобы сделать это предложение разумным ;-). –

+0

Если бы мне пришлось принять все эти спецификации как должное, я бы сказал, что «используйте базу данных» xD – fortran

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

будет, вероятно, будет быстрее, чем методы, список понимание в среднем, потому что не пройти весь список, если он находит деталь в вопросе рано.

+0

поднимет 'KeyError', если dict не имеет' id'. и это не то, о чем попросил ОП. – SilentGhost

+0

@Imagist +1 Это именно то, что я искал. Примечание к @SilentGhost: вы можете просто использовать другой ключ, отличный от 'id', если вы хотите настроить таргетинг на другое значение, то есть:' if ls [i] ['name'] == 'john': 'будет соответствовать и удалите этот словарь. – natureminded

Смежные вопросы