Оптимизация: перевод множеств на использование словарей Python

optimizaciya perevod mnozhestv na ispolzovanie slovarej python Структуры данных

Первой проблемой, с которой можно столкнуться, начав использовать класс Set, оказывается производительность: его вложенные циклы for и проверки in замедляют выполнение с экспоненциальной скоростью. Библиотечные классы должны быть реализованы с максимально возможной эффективностью, иначе такая медлительность может оказаться весьма существенной для некоторых приложений.

Один из способов оптимизировать производительность множеств состоит в переходе к использованию словарей вместо списков для внутреннего хранения множеств — элементы могут храниться в виде ключей словаря, все значения в котором равны None. Так как время поиска в словарях постоянно и невелико, проверку в списке оператором in в первоначальном множестве в этой схеме можно заменить прямой выборкой из словаря. Говоря традиционным языком, при переходе к использованию словарей мы заменяем медлительный линейный поиск быстрыми хеш- таблицами. Программист мог бы пояснить, что повторяющееся сканирование списков в поисках пересечений является экс по нен ци аль ным алгоритмом, а выборка из словарей — ли нейным.

Эту идею реализует модуль в примере 18.11. Его класс является подклассом оригинального множества и переопределяет методы, связанные с внутренним представлением, но наследует остальные. Унаследованные методы перегрузки операторов & и | вызывают здесь новые методы intersect и union, а унаследованный метод len действует со словарями без изменений. Пока клиенты класса Set не зависят от порядка элементов в множестве, они могут непосредственно переключиться на эту версию, просто изменив имя модуля, из которого импортируется класс Set, — имя класса остается прежним.

Пример 18.11. PP4E\Dstruct\Basic\fastset.py

"оптимизация за счет применения линейного алгоритма сканирования по словарям"

import set

# fastset.Set расширяет set.Set

class Set(set.Set):

def __init__(self, value = []):

self.data = {} # управляет локальным словарем

self.concat(value) # хеширование: время поиска изменяется линейно

def intersect(self, other):

res = {}

for x in other: # other: последовательность или Set

if x in self.data: # поиск по хештаблицам; 3.X

res[x] = None

return Set(res.keys()) # новый экземпляр Set на основе словаря

def union(self, other):

res = {} # other: последовательность или Set

for x in other: # сканировать каждое множество только 1 раз

res[x] = None

for x in self.data.keys(): # ‘&’ и ‘|’ попадают сюда

res[x] = None # так они создают новые быстрые множества

return Set(res.keys())

def concat(self, value):

for x in value: self.data[x] = None

# inherit and, or, len

def __getitem__(self, ix):

return list(self.data.keys())[ix] # 3.X: вызов list() необходим

def __repr__(self):

return ‘<Set:%r>’ % list(self.data.keys()) # то же самое

Эта версия действует точно так же, как предыдущая, хотя ее внутренняя реализация радикально отличается:

>   >> from fastset import Set

>   >> users1 = Set([‘Bob’, ‘Emily’, ‘Howard’, ‘Peeper’])

>   >> users2 = Set([‘Jerry’, ‘Howard’, ‘Carol’])

>   >> users3 = Set([‘Emily’, ‘Carol’])

>   >> users1 & users2

<Set:[‘Howard’]>

>   >> users1 | users2

<Set:[‘Howard’, ‘Peeper’, ‘Jerry’, ‘Carol’, ‘Bob’, ‘Emily’]>

>   >> users1 | users2 & users3

<Set:[‘Peeper’, ‘Carol’, ‘Howard’, ‘Bob’, ‘Emily’]>

>   >> (users1 | users2) & users3

<Set:[‘Carol’, ‘Emily’]>

>   >> users1.data

{‘Peeper’: None, ‘Bob’: None, ‘Howard’: None, ‘Emily’: None}

Главным функциональным отличием этой версии является порядок следования элементов в множестве: поскольку словари упорядочиваются произвольным образом, порядок будет отличаться от исходного. Более того, порядок следования может быть разным даже в разных версиях Python (фактически такая разница наблюдается между Python 2.X и 3.X в третьем и четвертом изданиях этой книги). Например, можно хранить в множествах составные объекты, но порядок элементов в этой версии отличается:

>   >> import set, fastset

>   >> a = set.Set([(1,2), (3,4), (5,6)])

>   >> b = set.Set([(3,4), (7,8)])

>   >> a & b

<Set:[(3, 4)]>

>   >> a | b

>   Set:[(1, 2), (3, 4), (5, 6), (7, 8)]>

>   >> a = fastset.Set([(1,2), (3,4), (5,6)])

>   >> b = fastset.Set([(3,4), (7,8)])

>   >> a & b

<Set:[(3, 4)]>

>   >> a | b

>   Set:[(1, 2), (5, 6), (3, 4), (7, 8)]>

>   >> b | a

>   Set:[(1, 2), (5, 6), (3, 4), (7, 8)]>

Как бы то ни было, но от множеств не требуется упорядоченность, поэтому здесь нет никаких проблем. А вот отклонение, которое может иметь значение, состоит в том, что в этой версии нельзя хранить не хе- шируе мые объекты. Это следует из того, что ключи словаря должны быть неизменяемыми. Так как значения хранятся в ключах, множества, основанные на словарях, могут содержать только такие элементы, как кортежи, строки, числа и объекты классов, объявленные как неизменяемые. Изменяемые объекты, такие как списки и словари, нельзя использовать непосредственно в таких множествах на основе словарей, но можно в множествах оригинального класса. Однако в качестве составных элементов множеств вполне допустимо использовать кортежи, потому что они являются неизменяемыми — при их использовании интерпретатор будет вычислять хеш-значения и проверять равенство ключей, как обычно:

>  >> set.Set([[1, 2],[3, 4]])

>  Set:[[1, 2], [3, 4]]>

>  >> fastset.Set([[1, 2],[3, 4]])

TypeError: unhashable type: ‘list’

>  >> x = fastset.Set([(1, 2), (3, 4)])

>>> x & fastset.Set([(3, 4), (1, 5)])

<Set:[(3, 4)]>

Использованная литература:

Марк Лутц — Программирование на Python, 4-е издание, II том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий