Python получает случайный ключ в словаре в O (1)
Мне нужна структура данных, которая поддерживает FAST-вставку и удаление пар (ключ, значение), а также "получить случайный ключ", что делает то же самое, что и random.choice(dict.keys()) для словаря, Я искал в Интернете, и большинство людей, похоже, удовлетворены методом random.choice(dict.keys()), несмотря на то, что это линейное время.
Я знаю, что выполнение этого быстрее возможно:
- Я мог бы использовать хэш-таблицу изменения размера. Если я утверждаю, что отношение ключей к слотам составляет от 1 до 2, тогда я могу просто выбрать случайные индексы, пока не нахожусь в непустой слот. Я смотрю только на 1 - 2 клавиши, ожидая.
- Я могу получить эти операции в гарантированном наихудшем случае O (log n), используя дерево AVL, дополняя его рангом.
Есть ли простой способ получить это на Python? Кажется, должно быть!
Ответы
Ответ 1
Это может не иметь особого отношения к конкретному варианту использования, указанному выше, но это вопрос, который я задаю при поиске способа красиво получить "любой" ключ в словаре.
Если вам не нужен действительно случайный выбор, а просто нужен какой-то произвольный ключ, вот два простых варианта, которые я нашел:
key = next(iter(d)) # may be a little expensive, but presumably O(1)
Второй действительно полезен только в том случае, если вы счастливы использовать ключ + значение из словаря, и из-за мутации (-ов) это не будет столь же алгоритмически эффективным:
key, value = d.popitem() # may not be O(1) especially if next step
if MUST_LEAVE_VALUE:
d[key] = value
Ответ 2
[edit: Полностью переписано, но сохраняю вопрос здесь с комментариями нет.]
Ниже представлена реализация словарной оболочки с O (1) get/insert/delete и выбор O (1) случайного элемента.
Основная идея заключается в том, что мы хотим иметь O (1), но произвольное отображение от range(len(mapping))
к ключам. Это позволит нам получить random.randrange(len(mapping))
и передать его через отображение.
Это очень сложно реализовать, пока вы не поймете, что мы можем воспользоваться тем, что отображение может быть произвольным. Ключевой идеей для достижения жесткой границы времени O (1) является следующее: всякий раз, когда вы удаляете элемент, вы меняете его с наивысшим элементом произвольного идентификатора и обновляете любые указатели.
class RandomChoiceDict(object):
def __init__(self):
self.mapping = {} # wraps a dictionary
# e.g. {'a':'Alice', 'b':'Bob', 'c':'Carrie'}
# the arbitrary mapping mentioned above
self.idToKey = {} # e.g. {0:'a', 1:'c' 2:'b'},
# or {0:'b', 1:'a' 2:'c'}, etc.
self.keyToId = {} # needed to help delete elements
Получить, установить и удалить:
def __getitem__(self, key): # O(1)
return self.mapping[key]
def __setitem__(self, key, value): # O(1)
if key in self.mapping:
self.mapping[key] = value
else: # new item
newId = len(self.mapping)
self.mapping[key] = value
# add it to the arbitrary bijection
self.idToKey[newId] = key
self.keyToId[key] = newId
def __delitem__(self, key): # O(1)
del self.mapping[key] # O(1) average case
# see http://wiki.python.org/moin/TimeComplexity
emptyId = self.keyToId[key]
largestId = len(self.mapping) # about to be deleted
largestIdKey = self.idToKey[largestId] # going to store this in empty Id
# swap deleted element with highest-id element in arbitrary map:
self.idToKey[emptyId] = largestIdKey
self.keyToId[largestIdKey] = emptyId
del self.keyToId[key]
del self.idToKey[largestId]
Выбор случайного (ключ, элемент):
def randomItem(self): # O(1)
r = random.randrange(len(self.mapping))
k = self.idToKey[r]
return (k, self.mapping[k])
Ответ 3
Вот несколько запутанный подход:
- Назначьте индекс каждой клавише, сохраняя ее со значением в словаре.
- Сохраняйте целое число, представляющее следующий индекс (позвольте этому next_index).
- Сохранять связанный список удаленных индексов (пробелов).
- Держите словарь, сопоставляющий индексы с ключами.
- При добавлении ключа проверьте использование (и удалите) первый индекс в связанном списке в качестве индекса, или если список пуст, используйте и увеличивайте значение next_index. Затем добавьте ключ, значение и индекс в словарь (
dictionary[key] = (index, value)
) и добавьте ключ в словарь с индексом-ключом (indexdict[index] = key
).
- При удалении ключа, получите индекс из словаря, удалите ключ из словаря, удалите индекс из словаря индекса-ключа и вставьте индекс в начало связанного списка.
- Чтобы получить случайный ключ, получите случайное целое, используя что-то вроде
random.randrange(0, next_index)
. Если индекс не находится в словаре "ключ-к-индексу", повторите попытку (это должно быть редко).
Вот реализация:
import random
class RandomDict(object):
def __init__(self): # O(1)
self.dictionary = {}
self.indexdict = {}
self.next_index = 0
self.removed_indices = None
self.len = 0
def __len__(self): # might as well include this
return self.len
def __getitem__(self, key): # O(1)
return self.dictionary[key][1]
def __setitem__(self, key, value): # O(1)
if key in self.dictionary: # O(1)
self.dictionary[key][1] = value # O(1)
return
if self.removed_indices is None:
index = self.next_index
self.next_index += 1
else:
index = self.removed_indices[0]
self.removed_indices = self.removed_indices[1]
self.dictionary[key] = [index, value] # O(1)
self.indexdict[index] = key # O(1)
self.len += 1
def __delitem__(self, key): # O(1)
index = self.dictionary[key][0] # O(1)
del self.dictionary[key] # O(1)
del self.indexdict[index] # O(1)
self.removed_indices = (index, self.removed_indices)
self.len -= 1
def random_key(self): # O(log(next_item/len))
if self.len == 0: # which is usually close to O(1)
raise KeyError
while True:
r = random.randrange(0, self.next_index)
if r in self.indexdict:
return self.indexdict[r]
Ответ 4
У меня была та же проблема, и я написал
https://github.com/robtandy/randomdict
Я надеюсь, что это поможет! Он обеспечивает O (1) доступ к случайным клавишам, значениям или элементам.