我正在寻找一个好的数据结构来包含一个带有(hash, timestamp)
值的元组列表。基本上,我想以下列方式使用它:
我希望定期删除并返回一个早于特定时间戳的元组列表(我需要在它们过期时更新其他各种元素)。时间戳不必是任何特定的(它可以是unix时间戳,python datetime
对象,或其他一些易于比较的哈希/字符串)。
我使用它来接收传入的数据,如果它已经存在则更新它并清除超过X秒/分钟的数据。
多个数据结构也可以是一个有效的建议(我最初使用优先级队列+集合,但优先级队列不是最佳值,以便不断更新值)。
同样欢迎实现同样目标的其他方法。最终目标是跟踪元素是a)系统的新内容,b)系统中是否存在以及c)何时到期。
答案 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树(以哈希为键)。
通过将最近访问过的(并因此更新的)节点旋转到根节点,您最终应该在叶子上最近访问(并因此更新)数据,或者在右子树中分组。
确定细节(并实施它们)留给读者练习......
注意事项:
更简单的方法是使用(hash, timestamp, prev, next)
和prev
将包含next
的对象存储在常规字典中,以保留最新的双向链接列表。然后,您需要的所有内容都是head
和tail
引用。
插入&amp;更新仍然是恒定时间(哈希查找+链接列表拼接),并从列表尾部向后走,收集最旧的哈希是线性的。
答案 2 :(得分:2)
除非我误解你的问题,否则一个普通的dict
应该是除了清除之外的所有事情的理想选择。假设您试图避免在清除期间检查整个字典,我建议保留第二个数据结构以保留(timestamp, hash)
对。
此补充数据结构可以是普通的list
或deque
(来自collections
模块)。可能bisect
模块可以方便地将时间戳比较的数量减少到最小值(而不是比较所有时间戳,直到达到截止值),但是因为你仍然需要按顺序迭代对于需要清除的物品,要弄清楚最快的确切细节需要进行一些测试。
修改强>
对于Python 2.7或3.1+,您还可以考虑使用OrderedDict
(来自collections
模块)。这基本上是一个dict
,在类中内置了一个补充保持订单的数据结构,因此您不必自己实现它。唯一的障碍是它保留的仅顺序是插入顺序,因此为了您的目的,您不需要将现有条目重新分配给新时间戳,而是需要将其删除(使用{{ 1}})然后使用新的时间戳分配一个新条目。但是,它保留了O(1)查找,使您不必自己维护del
对的列表;当需要清除时,您可以直接遍历(timestamp, hash)
,删除条目,直到您找到时间戳晚于截止时间的条目。
答案 3 :(得分:1)
如果您可以偶尔解决误报,我认为布隆过滤器可能很适合您的需求(非常快)
编辑:再次阅读你的帖子,我认为这会有效,但不要存储哈希,只需让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