Удалите словарь из списка

Если я имею список словарей, говорю:

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

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

51
задан Georgy 4 November 2019 в 00:53
поделиться

6 ответов

class Bob
end

def create(name)
  return eval("#{name}.new")
end

b = create(:Bob)
puts b.class

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

$ 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

где перетасовка может быть удалена, потому что, как мы знаем, уже есть три слова с равным интервалом, которые нужно удалить. И 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 микросекунд в более крупном ;-). Избегание или критика простых, понятных и совершенно адекватных по производительности решений (например, составления списков для этого общего класса задач «удалить некоторые элементы из списка») - особенно неприятный пример хорошо известного тезиса Кнута и Хоара о том, что «преждевременная оптимизация - это корень всего зла в программировании »! -)

94
ответ дан 7 November 2019 в 10:00
поделиться

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

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)

Если вы не можете открывать с начала - открывать с конца, это не нарушит цикл for.

1
ответ дан 7 November 2019 в 10:00
поделиться

Вот способ сделать это с пониманием списка (при условии, что вы назвали свой список 'foo'):

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

Substitute '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 будет возвращать в любом случае итератор, так что, как предположил Алекс, составление списка действительно лучший выбор.

8
ответ дан 7 November 2019 в 10:00
поделиться

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

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 для найти нужный объект несколько быстрее.

0
ответ дан 7 November 2019 в 10:00
поделиться
# assume ls contains your list
for i in range(len(ls)):
    if ls[i]['id'] == 2:
        del ls[i]
        break

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

3
ответ дан 7 November 2019 в 10:00
поделиться

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

7
ответ дан 7 November 2019 в 10:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: