理想的数据结构,快速查找,快速更新和易于比较/排序

时间:2013-10-09 15:42:24

标签: python data-structures

我正在寻找一个好的数据结构来包含一个带有(hash, timestamp)值的元组列表。基本上,我想以下列方式使用它:

  • 数据进入,检查它是否已经存在于数据结构中(哈希等式,而不是时间戳)。
  • 如果是,请将时间戳更新为“now”
  • 如果没有,请将其添加到时间戳为“now”的集合中

我希望定期删除并返回一个早于特定时间戳的元组列表(我需要在它们过期时更新其他各种元素)。时间戳不必是任何特定的(它可以是unix时间戳,python datetime对象,或其他一些易于比较的哈希/字符串)。

我使用它来接收传入的数据,如果它已经存在则更新它并清除超过X秒/分钟的数据。

多个数据结构也可以是一个有效的建议(我最初使用优先级队列+集合,但优先级队列不是最佳值,以便不断更新值)。

同样欢迎实现同样目标的其他方法。最终目标是跟踪元素是a)系统的新内容,b)系统中是否存在以及c)何时到期。

5 个答案:

答案 0 :(得分:4)

这是一个非常好的步骤空间。你需要的是两个结构,你需要一些东西来告诉你你的密钥(在你的情况下是hash)是集合所知道的。为此,dict非常合适;我们只需将hash映射到timestamp,这样您就可以轻松查找每个项目。按时间戳顺序迭代项目是一项特别适用于由heapq模块提供的堆的任务。每当我们看到一个键时,我们只需将它添加到我们的堆中,作为(timestamp, hash)的元组。

不幸的是,没有办法查看堆化列表并删除某些项目(因为,例如,它们已更新为稍后过期)。我们可以通过忽略堆中具有与dict中的值不同的时间戳的条目来解决这个问题。

所以这里是一个开始的地方,您可以向包装类添加方法以支持其他操作,或者更改数据的存储方式:

import heapq


class ExpiringCache(object):
    def __init__(self):
        self._dict = {}
        self._heap = []

    def add(self, key, expiry):
        self._dict[key] = expiry
        heapq.heappush(self._heap, (expiry, key))

    def contains(self, key):
        return key in self._dict

    def collect(self, maxage):
        while self._heap and self._heap[0][0] <= maxage:
            expiry, key = heapq.heappop(self._heap)
            if self._dict.get(key) == expiry:
                del self._dict[key]

    def items(self):
        return self._dict.items()

创建缓存并添加一些项目

>>> xc = ExpiringCache()
>>> xc.add('apples', 1)
>>> xc.add('bananas', 2)
>>> xc.add('mangoes', 3)

重新添加一个甚至有效期到期的项目

>>> xc.add('apples', 4)

收集比两个时间单位“更旧”的所有内容

>>> xc.collect(2)    
>>> xc.contains('apples')
True
>>> xc.contains('bananas')
False

答案 1 :(得分:3)

我能想到的具有所需属性的单个结构最接近的是一个splay树(以哈希为键)。

通过将最近访问过的(并因此更新的)节点旋转到根节点,您最终应该在叶子上最近访问(并因此更新)数据,或者在右子树中分组。

确定细节(并实施它们)留给读者练习......


注意事项:

  • 最坏情况下的高度 - 因此复杂性 - 是线性的。使用合适的哈希值不会发生这种情况
  • 任何只读操作(即不更新时间戳的查找)都会破坏splay树布局和时间戳之间的关系

更简单的方法是使用(hash, timestamp, prev, next)prev将包含next的对象存储在常规字典中,以保留最新的双向链接列表。然后,您需要的所有内容都是headtail引用。

插入&amp;更新仍然是恒定时间(哈希查找+链接列表拼接),并从列表尾部向后走,收集最旧的哈希是线性的。

答案 2 :(得分:2)

除非我误解你的问题,否则一个普通的dict应该是除了清除之外的所有事情的理想选择。假设您试图避免在清除期间检查整个字典,我建议保留第二个数据结构以保留(timestamp, hash)对。

此补充数据结构可以是普通的listdeque(来自collections模块)。可能bisect模块可以方便地将时间戳比较的数量减少到最小值(而不是比较所有时间戳,直到达到截止值),但是因为你仍然需要按顺序迭代对于需要清除的物品,要弄清楚最快的确切细节需要进行一些测试。

修改

对于Python 2.7或3.1+,您还可以考虑使用OrderedDict(来自collections模块)。这基本上是一个dict,在类中内置了一个补充保持订单的数据结构,因此您不必自己实现它。唯一的障碍是它保留的顺序是插入顺序,因此为了您的目的,您不需要将现有条目重新分配给新时间戳,而是需要将其删除(使用{{ 1}})然后使用新的时间戳分配一个新条目。但是,它保留了O(1)查找,使您不必自己维护del对的列表;当需要清除时,您可以直接遍历(timestamp, hash),删除条目,直到您找到时间戳晚于截止时间的条目。

答案 3 :(得分:1)

如果您可以偶尔解决误报,我认为布隆过滤器可能很适合您的需求(非常快)

http://en.wikipedia.org/wiki/Bloom_filter

和python实现:https://github.com/axiak/pybloomfiltermmap

编辑:再次阅读你的帖子,我认为这会有效,但不要存储哈希,只需让bloomfilter为你创建哈希。即,我认为你只想使用bloomfilter作为一组时间戳。我假设您的时间戳基本上只是一组,因为您正在对它们进行哈希处理。

答案 4 :(得分:0)

对于检查/更新/设置操作,简单的哈希表或字典将是O(1)。您可以将数据同时存储在一个简单的按时间排序的列表中,用于清除操作。保持一个头尾指针,这样插入也是O(1),并且删除就像推进头部一样简单,直到达到目标时间并删除你从哈希中找到的所有条目。

开销是每个存储数据项的一个额外指针,代码很简单:

insert(key,time,data):
  existing = MyDictionary.find(key)
  if existing:  
      existing.mark()
  node = MyNodeType(data,time)  #simple container holding args + 'next' pointer
  node.next = NULL
  MyDictionary.insert(key,node)
  Tail.next = node
  if Head is NULL:  Head = node

clean(olderThan):
  while Head.time < olderThan:
    n = Head.next 
    if not Head.isMarked():  
        MyDictionary.remove(Head.key)
    #else it was already overwritten
    if Head == Tail: Tail = n
    Head = n