Самый быстрый способ вытолкнуть N предметов из большой диктовки - PullRequest
19 голосов
/ 16 марта 2019

У меня большой дикт src (до 1М предметов), и я хотел бы взять N (типичные значения N = 10K-20K), сохранить их в новом дикте dst и оставить только остальные пункты в src. Неважно, какие N предметов взяты. Я ищу самый быстрый способ сделать это на Python 3.6 или 3.7.

Самый быстрый подход, который я нашел до сих пор:

src = {i: i ** 3 for i in range(1000000)}

# Taking items 1 by 1 (~0.0059s)
dst = {}
while len(dst) < 20000:
    item = src.popitem()
    dst[item[0]] = item[1]

Есть что-нибудь лучше? Хорошо бы даже незначительный выигрыш.

Ответы [ 3 ]

19 голосов
/ 16 марта 2019

Простое понимание внутри dict сделает:

dict(src.popitem() for _ in range(20000))

Здесь у вас есть временные тесты

setup = """
src = {i: i ** 3 for i in range(1000000)}

def method_1(d):
  dst = {}
  while len(dst) < 20000:
      item = d.popitem()
      dst[item[0]] = item[1]
  return dst

def method_2(d):
  return dict(d.popitem() for _ in range(20000))
"""
import timeit
print("Method 1: ", timeit.timeit('method_1(src)', setup=setup, number=1))

print("Method 2: ", timeit.timeit('method_2(src)', setup=setup, number=1))

Результаты:

Method 1:  0.007701821999944514
Method 2:  0.004668198998842854
9 голосов
/ 16 марта 2019

Это еще немного быстрее:

from itertools import islice
def method_4(d):
    result = dict(islice(d.items(), 20000))
    for k in result: del d[k]
    return result

По сравнению с другими версиями, используя тестовый пример Netwave:

Method 1:  0.004459443036466837  # original
Method 2:  0.0034434819826856256 # Netwave
Method 3:  0.002602717955596745  # chepner
Method 4:  0.001974945073015988  # this answer

Дополнительное ускорение, по-видимому, связано с отказом от перехода между функциями C и Python. Из разборки мы можем заметить, что создание экземпляра dict происходит на стороне C, с только 3 вызовами функций из Python. Цикл использует DELETE_SUBSCR код операции вместо необходимости вызова функции:

>>> dis.dis(method_4)
  2           0 LOAD_GLOBAL              0 (dict)
              2 LOAD_GLOBAL              1 (islice)
              4 LOAD_FAST                0 (d)
              6 LOAD_ATTR                2 (items)
              8 CALL_FUNCTION            0
             10 LOAD_CONST               1 (20000)
             12 CALL_FUNCTION            2
             14 CALL_FUNCTION            1
             16 STORE_FAST               1 (result)

  3          18 SETUP_LOOP              18 (to 38)
             20 LOAD_FAST                1 (result)
             22 GET_ITER
        >>   24 FOR_ITER                10 (to 36)
             26 STORE_FAST               2 (k)
             28 LOAD_FAST                0 (d)
             30 LOAD_FAST                2 (k)
             32 DELETE_SUBSCR
             34 JUMP_ABSOLUTE           24
        >>   36 POP_BLOCK

  4     >>   38 LOAD_FAST                1 (result)
             40 RETURN_VALUE

По сравнению с итератором в method_2:

>>> dis.dis(d.popitem() for _ in range(20000))
  1           0 LOAD_FAST                0 (.0)
        >>    2 FOR_ITER                14 (to 18)
              4 STORE_FAST               1 (_)
              6 LOAD_GLOBAL              0 (d)
              8 LOAD_ATTR                1 (popitem)
             10 CALL_FUNCTION            0
             12 YIELD_VALUE
             14 POP_TOP
             16 JUMP_ABSOLUTE            2
        >>   18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

, для которого требуется вызов функции Python to C для каждого элемента.

3 голосов
/ 16 марта 2019

Я обнаружил, что этот подход немного быстрее (скорость -10%), используя словарное понимание, которое использует цикл, используя range, который возвращает и распаковывает ключи и значения

dst = {key:value for key,value in (src.popitem() for _ in range(20000))}

на моей машине:

your code: 0.00899505615234375
my code:   0.007996797561645508

так примерно на 12% быстрее, неплохо, но не так хорошо, как не распаковывать, как Простой ответ Netwave

Этот подход может быть полезен, если вы хотите преобразовать ключи или значения в процессе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...