Самый быстрый способ создать подсказку из списка, где ключ == значение - PullRequest
0 голосов
/ 04 октября 2018

У меня есть список, скажем:

NUM = 100
my_list = list(range(NUM))

Я хотел бы сгенерировать dict, где ключ равен значению, что-то вроде:

my_dict = {item: item for item in my_list}

или:

my_dict = dict(zip(my_list, my_list))

Я запустил несколько микро-тестов, и похоже, что они имеют одинаковую скорость, но я надеялся, что второй будет намного быстрее, так как цикл должен происходить в C.

Например, следующая конструкция:

my_dict = {key: SOMETHING for key in keys}

переводится в гораздо более быстрое:

my_dict = dict.fromkeys(k, SOMETHING)

Итак, мой вопрос: есть ли подобная такая конструкция для * 1019?* ???*


EDIT 2

Такой метод, как dict.fromitems(), будет иметь более широкое применение, чем этот конкретный пример использования, поскольку:

dict.fromitems(keys, values)

может в принципе заменить оба:

{k, v for k, v in zip(keys, values)}

и:

dict(zip(keys, values))

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Нет, нет более быстрого метода, доступного для словарей.

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

dict(zip(it, it)), {k: k for k in it} и dict.fromkeys(it) все близки по скорости:

>>> from timeit import Timer
>>> tests = {
...     'dictcomp': '{k: k for k in it}',
...     'dictzip': 'dict(zip(it, it))',
...     'fromkeys': 'dict.fromkeys(it)',
... }
>>> timings = {n: [] for n in tests}
>>> for magnitude in range(2, 8):
...     it = range(10 ** magnitude)
...     for name, test in tests.items():
...         peritemtimes = []
...         for repetition in range(3):
...             count, total = Timer(test, 'from __main__ import it').autorange()
...             peritemtimes.append(total / count / (10 ** magnitude))
...         timings[name].append(min(peritemtimes))  # best of 3
...
>>> for name, times in timings.items():
...     print(f'{name:>8}', *(f'{t * 10 ** 9:5.1f} ns' for t in times), sep=' | ')
...
dictcomp |  46.5 ns |  47.5 ns |  50.0 ns |  79.0 ns | 101.1 ns | 111.7 ns
 dictzip |  49.3 ns |  56.3 ns |  71.6 ns | 109.7 ns | 132.9 ns | 145.8 ns
fromkeys |  33.9 ns |  37.2 ns |  37.4 ns |  62.7 ns |  87.6 ns |  95.7 ns

Этотаблица стоимости каждой единицы техники от 100 до 10 миллионов единиц.Время увеличивается по мере накопления дополнительных затрат на наращивание структур хеш-таблиц.

Конечно, dict.fromkeys() может обрабатывать элементы на немного немного быстрее, но это не на порядок быстрее, чемдругие процессы.Это (небольшое) преимущество в скорости происходит не от возможности перебирать здесь C;разница заключается лишь в том, что не нужно обновлять указатель значения на каждой итерации;все ключи указывают на ссылку на одно значение.

zip() работает медленнее, поскольку создает дополнительные объекты (создание кортежа из 2 элементов для каждой пары ключ-значение не является бесплатнымоперации), и это увеличило число итераторов, вовлеченных в процесс, вы переходите от одного итератора для понимания словаря и dict.fromkeys() к 3 итераторам (делегированная итерация dict() через * 1027)*, к двум отдельным итераторам для ключей и значений).

Нет смысла добавлять отдельный метод в класс dict для обработки этого в C, потому что

  1. в любом случае не является достаточно распространенным вариантом использования (создание отображения с равными ключами и значениями не является общей потребностью)
  2. не будет значительно быстрее в C, чем это было бы со словаремпонимание в любом случае .
0 голосов
/ 04 октября 2018

Используя результаты ответа здесь , мы создаем новый класс, который подклассов defaultdict, и переопределяем его атрибут отсутствует , чтобы разрешить передачу ключа в default_factory:

from collections import defaultdict
class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        else:
            ret = self[key] = self.default_factory(key)
            return ret

Теперь вы можете создать вид словаря, который вы ищете, выполнив следующее:

my_dict = keydefaultdict(lambda x: x)

Затем, когда вам нужно сделать сопоставление для ключей, которые не отображаются насами вы просто обновляете эти значения.

Сроки.

Подклассы defaultdict:

%%timeit
my_dict = keydefaultdict(lambda x: x)
for num in some_numbers: my_dict[num] == num

Результаты:

4.46 s ± 71.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Понимание Dict

%%timeit
my_dict = {x: x for x in some_numbers}
for num in some_numbers: my_dict[num] == num

Результаты:

1.19 s ± 20.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Они становятся сопоставимыми, когда вам требуется приблизительно 17 доступа% от первоначальных значений.И лучше, если вам нужно меньше:

Доступ только к части исходных значений

Подклассы defaultdict:

%%timeit
frac = 0.17
my_dict = keydefaultdict(lambda x: x)
for num in some_numbers[:int(len(some_numbers)*frac)]: my_dict[num] == num

Результаты:

770 ms ± 4.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Понимание Dict

%%timeit
frac = 0.175
my_dict = {x: x for x in some_numbers}
for num in some_numbers[:int(len(some_numbers)*frac)]: my_dict[num] == num

Результаты:

781 ms ± 4.03 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...