2009-09-29 5 views
6

Иногда имеет смысл иметь словарь с ключом. В C++ это часто реализуется с красно-черным деревом. Но любое самобалансирующееся двоичное дерево поиска будет делать (fwiw, Knuth особенно ясен по этому вопросу). Лучшим решением, которое я смог придумать до сих пор, является принятие R. McGraw's AVL-tree type и создание класса-оболочки, который в основном реализует интерфейс карты STL (также рассчитывая на удобное упорядочение пар (двух элементов кортежей) в Python). Такой кортеж в основном соответствует std :: map :: value_type.Mapping std :: map to Python

Да, есть модуль Bisect Python, и хотя это логарифмически во время вставки так же, как самобалансирующиеся двоичные деревья логарифмичны во время вставки (справа?), Честно говоря, я просто хочу объект. Вызывается OrderedDict или что-то (и нет, Python 3.1 OrderedDict не подходит - это для заказа «вставки-времени» - и, откровенно говоря, то, что порядок ввода-времени имеет отношение к заказу, не совсем очевидно).

Примечание. Словарь, упорядоченный по ключевым словам, очень полезен во многих отраслях промышленности (например, в финансах, например, для отслеживания ценных книг данных, которые являются в основном упорядоченными словарями цены -> количества, агрегированной информации о заказе и т. Д. .).

Если у кого-то есть другие идеи, это здорово. Все, что я знаю, я только что получил в пять миллионов раз умнее ответы Алекса Мартелли. Поэтому я подумал, что спрошу.

+0

спасибо всем! у меня нет достаточного количества очков или чего-то, что бы поддержать всех, но я считаю, что все комментарии очень полезны. – 2009-09-29 14:19:21

ответ

-1

Как вы сказали, вы можете свернуть свою собственную реализацию с Bisect:

class OrderedDict: 
    def __init__(self, keyvalues_iter): 
    self.__srtlst__ = sorted(keyvalues_iter) 
    def __len__(self): 
    return len(self.__srtlst__) 
    def __contains__(self, key): 
    index = bisect(self.__srtlst__, key) 
    if index<len(self.__srtlst__) and self.__srtlst__[index][0] == key: 
     return True 
    else: 
     return False  
    def __getitem__(self, key): 
    index = bisect(self.__srtlst__, key) 
    if index<len(self.__srtlst__) and self.__srtlst__[index][0] == key: 
     return self.__srtlst__[index][1] 
    else: 
     raise KeyError(key) 
    def __setitem__(sekf, key, value): 
    index = bisect(self.__srtlst__, key) 
    if index<len(self.__srtlst__) and self.__srtlst__[index][0] == key: 
     self.__srtlst__[index][1] = value 
    else: 
     self.__srtlst__[index]=(key, value) 
    def __delitem__(sekf, key, value): 
    index = bisect(self.__srtlst__, key) 
    if index<len(self.__srtlst__) and self.__srtlst__[index][0] == key: 
     del __srtlst__[index] 
    else: 
     raise KeyError(key) 
    def __iter__(self): 
    return (v for k,v in self.__srtlst__) 
    def clear(self): 
    self.__srtlst__ = [] 
    def get(self, key, default=None): 
    index = bisect(self.__srtlst__, key) 
    if index<len(self.__srtlst__) and self.__srtlst__[index][0] == key: 
     return self.__srtlst__[index][1] 
    else: 
     return default 
    def items(self): 
    return self.__srtlst__[:] 
    def iteritems(self): 
    return iter(self.__srtlst__) 
    def iterkeys(self): 
    return (k for k,v in self.__srtlst__) 
    def itervalues(self): 
    return (v for k,v in self.__srtlst__) 
    def keys(self): 
    return [k for k,v in self.__srtlst__] 
    def values(self): 
    return [v for k,v in self.__srtlst__] 
    def setdefault(self, key, default): 
    index = bisect(self.__srtlst__, key) 
    if index<len(self.__srtlst__) and self.__srtlst__[index][0] == key: 
     return self.__srtlst__[index][1] 
    else: 
     self.__srtlst__[index]=(key, default) 
     return default 
    def update(self, other): 
    #a more efficient implementation could be done merging the sorted lists 
    for k, v in other.iteritems(): 
     self[k] = v 

хммм ... мне кажется, что я уже сделал это за вас, d'ах!

+0

Единственным недостатком этой реализации afaik является то, что вставка и удаление возможно потенциально O (n) (даже если вы можете найти индекс в O (log n), если вам нужно переместить элементы, чтобы освободить место для нового элемента или удалить существующий элемент, вам может потребоваться сместить базовый отсортированный список в O (n) времени). слегка измененная версия доступна здесь: http://pastebin.com/f1e721bdb (но до сих пор нет метода вставки) – 2009-09-29 15:31:03

+0

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

+3

«Единственным недостатком» являются O (n) модификации вместо O (log n) или O (1)? Это не второстепенный недостаток, это потеря фундаментальной выгоды от дерева. Если все, что вам понадобится в данный момент, это то, что вы можете получить от этого, здорово, но это не std :: map. –

2

У меня точно такая же потребность, и ответ Алекс Мартелли полностью убедил меня: лучше всего хранить словарь и список частично отсортированных ключей, а затем сортировать по мере необходимости. Это эффективно из-за очень специфического поведения алгоритма сортировки python (AKA Timsort). Key-ordered dict in Python

Я проверил его осуществления и мой, и был его лучшим (потому что он не вставляет в середине списка)

(я настоятельно советую вам прочитать статью, связанную с комментарием АМ о timsort , что является жемчужиной).

+0

Спасибо. Наконец я прочитал полный пост. Очень полезно. Что касается Alex Martelli и комментариев каждого, я действительно думаю, что могу попытаться использовать py-avl wrapper (как упоминалось выше в вашем сообщении). Это несколько необычный случай: но мне в основном нужно найти подпоследовательность (что-то вроде lower_bound и upper_bound в std :: map). Несмотря на то, что timsort кажется большим (и особенно полезно избегать сравнений, будучи более агрессивным при обмене данными), что-то вроде этого, я думаю, более естественным образом разрешено с помощью дерева. O (log n) insert/delete тоже приятно. – 2009-09-29 15:47:21

+0

Заключительный комментарий, и тогда я немного успокоюсь. Но чтобы уточнить, о чем я говорил выше. Это не так много, что я хочу найти подпоследовательности с lower_bound и upper_bound в log (n) time (bisect тоже может это сделать). Это значит, что впоследствии я захочу вставить элемент где-нибудь в подпоследовательности (например, отсортированный набор виджетов в макете, отсортированный по расстоянию от ссылочного виджета). Если у вас есть итератор к дереву (или связанный список в этом отношении - но lower_bound/upper_bound в связанном списке, конечно, O (n)), все готово. – 2009-09-29 16:11:24

+0

fwiw, вот одно решение, использующее этот py-avl (с небольшим патчем: http://pastebin.com/d80a3296): http://pastebin.com/f66ca441c (обратите внимание, что это решение, вероятно, неверно во всех отношениях Важным является метод iter_range - попытка эмулировать lower_bound/upper_bound, хотя это первый раз, когда я изучил пользовательские итераторы, поэтому, как я уже сказал, это, вероятно, неправильно.) – 2009-09-29 20:38:52

1

Списки являются жалким заменителем дерева.

Вставки должны перемещать весь список вокруг, чтобы освободить место; удаления необходимо переместить список назад. Добавление или удаление материала в пакетном режиме является прекрасным, когда это возможно, но это очень часто бывает, или принимает неестественные искажения, чтобы организовать его. Фундаментальным атрибутом дерева является то, что вставками и удалениями являются O (log n); никакое количество перемотки не превратит O (n) в O (log n).

Вставка элемента в дерево, когда вы уже знаете, куда он собирается идти, - O (1). Эквивалентно, удаление элемента из дерева на основе его узла также равно O (1). std :: map поддерживает оба этих параметра. Это O (n) со списком.

Другим фундаментальным свойством дерева является то, что итерация по диапазону значений равна O (1) для каждой итерации. Объединение списка и dict теряет это, потому что каждая итерация должна выполнять поиск по типу. (Этот подход не имеет такой проблемы.)

Деревья являются одними из самых основных типов данных. Недостаток Python типа контейнера дерева - бородавка. Возможно, существует библиотека сторонних разработчиков, например, одна из которых связана с «Неизвестным», которую я не пытался, поэтому я не могу ручаться за нее), но для нее нет стандартного типа Python.

+1

Должен признаться, что нехватка (стандартизованной) хеш-таблицы на C++ более сложна в повседневной работе, чем отсутствие в Python контейнера на основе дерева. Но тогда я никогда не сталкивался с таким случаем, когда структура была не настолько мала, чтобы просто сортировать ее. – Kylotan

+1

O (1) ввод элемента x: 'thelist.append (x)'; O (1) удаление из индекса i: 'thelist [i] = thelist [-1]; del thelist [-1] '. Это упорядочение perturb, но для небольших возмущений thelist.sort() - O (N) (спасибо, timsort! -), и это нужно выполнять только тогда, когда это действительно необходимо (поэтому для большинства шаблонов доступа вы получаете хорошую амортизированную производительность). Так, например, например. Отсутствие оптимизаций хвостовой рекурсии на Python, отсутствие контейнеров на деревьях редко бывает проблемой на практике (неформальная проверка источников C++ Google: std :: hashmap _many_ порядков чаще, чем std :: map! -). –

+0

Они предназначены для решения различных проблем. Фактически, вы подразумеваете реализацию вектора/растущего массива списка, а не связанный список. Кроме того, дерево - это всего лишь список списков, и у вас уже есть те, что находятся в Python. – fortran

0

Для списка, который сортируется, вы можете попробовать модуль heapq.

+1

не полностью отсортирован, куча просто накладывает порядок между уровнями дерева (отец с детьми), но не на том же уровне (братья и сестры), например, в допустимой куче от нижнего к более высокому, h [0] fortran

1

Я наткнулся на этот вопрос, нуждающийся в OrderedMap, и нашел свой ужас, что принятый ответ - полный мусор. Поэтому я свернул мой собственный, в случае, если кто-то считает это полезным:

from bisect import * 

class OrderedMap: 
    """Much less efficient than a dict, 
    but keys don't need to be hashable.""" 
    __default_arg = object() 
    def __init__(self, keyvalues_iter = None): 
     self.clear() 
     if keyvalues_iter is not None: 
      self.update(keyvalues_iter) 
    def clear(self): 
     self.__keys = [] 
     self.__values = [] 
    def __index(self, key): 
     if self.__keys: 
      index = bisect(self.__keys, key)-1 
      if self.__keys[index] == key: 
       return index 
     raise KeyError(key) 
    def __len__(self): 
     return len(self.__keys) 
    def __contains__(self, key): 
     try: 
      self.__index(key) 
      return True 
     except KeyError: 
      return False 
    def __getitem__(self, key): 
     index = self.__index(key) 
     return self.__values[index] 
    def __setitem__(self, key, value): 
     try: 
      index = self.__index(key) 
      # exists 
      self.__values[index] = value 
     except KeyError: 
      # new 
      index = bisect(self.__keys, key) 
      self.__keys.insert(index, key) 
      self.__values.insert(index, value) 
    def __delitem__(self, key): 
     index = self.__index(key) 
     self.__keys.pop(index) 
     self.__values.pop(index) 
    def __iter__(self): 
     return iter(self.__keys) 
    def get(self, key, default=__default_arg): 
     try: 
      return self[key] 
     except KeyError: 
      if default != OrderedMap.__default_arg: 
       return default 
      raise 
    def setdefault(self, key, default = None): 
     try: 
      return self[key] 
     except KeyError: 
      if default != OrderedMap.__default_arg: 
       self[key] = default 
       return default 
      raise 
    def items(self): 
     return zip(self.__keys, self.__values) 
    def iteritems(self): 
     return iter((self.__keys[x], self.__values[x]) 
        for x in xrange(len(self))) 
    def keys(self): 
     return self.__keys[:] 
    def iterkeys(self): 
     return iter(self.__keys) 
    def values(self): 
     return self.__values[:] 
    def itervalues(self): 
     return iter(self.__values) 
    def update(self, other): 
     for k, v in other.iteritems(): 
      self[k] = v 
    def __repr__(self): 
     s = ", ".join("%s: %s" % (repr(self.__keys[x]), 
            repr(self.__values[x])) 
         for x in xrange(len(self))) 
     return "OrderedMap{%s}" % (s,) 
+0

Не слишком уверен в полезности использования метода clear(), поскольку экземпляры в противном случае неизменяемы после создания - в отличие от принятого ответа. – martineau

0

Питон sortedcontainers модуль обеспечивает тип SortedDict данных для именно этих целей. Он использует измененную структуру данных типа B-дерева и написан на чистом Python. Модуль имеет 100% -ный охват тестирования и часы стресса. Хотя чистый-Python, быстрее C-реализации и имеет performance comparison для резервного копирования.

Потому что это чисто Python, установка ветер с пип:

pip install sortedcontainers 

Тогда вы просто:

from sortedcontainers import SortedDict 
help(SortedDict) 

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