如何解决Python最有效的方式来保留排序的数据
最有效的方式来跟踪数据的升序/降序。假设我有一个数据流,假设这是非常大的。样本流:
key,mod,value
5,add,1
2,3
4,2
2,rem,5
在阅读流时,我将其放在字典中以跟踪内容。例如,在上述迷你流的结尾,我将有一个带有{5:1,4:2}
的字典。其中add
表示该值增加了该值,而rem
表示您从该键中删除了很多。如果该值变为0,则从字典中删除该键。但是我还希望能够按顺序打印数据(但不必一直打印)。我确实想跟踪最高/最低键,以便知道最高/最低值何时更改。更改密钥或更改其值。
我现在的操作方式是相应地从字典中填充/删除键。这应该是常数O(1)。跟踪sorted_keys
列表,其中每个流都会检查新数字是否在词典中,如果不在列表中,则将执行bisect.insort_right(sorted_keys,key)
。因此,sorted_keys
始终都在排序。假设在排序列表中添加1个值很快,尽管它确实需要扩展大小,因此这可能会使O(n)保持不变。并且我跟踪prev_highest
或prev_lowest
,并分别针对sorted_keys [0]或sorted_keys [-1]进行检查。
我尝试将双端队列与bisect.insort_right,sortedcontainers中的SortedDict,链接列表,OrderedDict一起使用,但似乎上述方法似乎效果最好。还有另一种可能的实现方式可以进一步优化吗?还是我应该按顺序跟踪某个级别,例如说10个项目。并相应地进行更新。但是,这样做的问题是,如果有新密钥,我如何知道它是否是新密钥之一?似乎拥有一个heapq会有所帮助,但是直到弹出它们,我才能获得排序后的值。而且,如果我需要按顺序打印整个内容,则只需对整个字典的键进行排序。
编辑: 使用下面的bisect和SortedDict添加我的测试:
import timeit
import bisect
import random
from sortedcontainers import SortedDict
NUM_ITERATION_TEST = 10
TOTAL_NUM_DATA = 1000000
MODS = ['add','rem']
QUANTITY = [1,5,10,20,100,200,300,500,1000]
DATA = [{'mod': random.choice(MODS),'key': random.randint(0,1000),'val': random.choice(QUANTITY)} for x in range(TOTAL_NUM_DATA)]
def method1(DATA):
d = {}
sorted_keys = []
for data in DATA:
if data['mod'] == 'add':
key = data['key']
if key in d.keys():
d[key] += data['val']
else:
d[key] = data['val']
bisect.insort_right(sorted_keys,key)
elif data['mod'] == 'rem':
key = data['key']
if key in d.keys():
if d[key] <= data['val']:
del d[key]
sorted_keys.remove(key)
else:
d[key] -= data['val']
else:
pass # Deleting something not there yet
def method2(DATA):
d = SortedDict()
for data in DATA:
if data['mod'] == 'add':
key = data['key']
if key in d.keys():
d[key] += data['val']
else:
d[key] = data['val']
elif data['mod'] == 'rem':
key = data['key']
if key in d.keys():
if d[key] <= data['val']:
del d[key]
else:
d[key] -= data['val']
else:
pass # Deleting something not there yet
if __name__ == "__main__":
# METHOD 1
print("Method 1 Execution Time:")
print(timeit.timeit("test_timeit.method1(test_timeit.DATA)",number=NUM_ITERATION_TEST,setup="import test_timeit"))
# METHOD 2
print("Method 2 Execution Time:")
print(timeit.timeit("test_timeit.method2(test_timeit.DATA)",setup="import test_timeit"))
以上结果为:
Method 1 Execution Time:
4.427699800000001
Method 2 Execution Time:
12.7445671
解决方法
对于适合内存的数据,“ SortedDict from sortedcontainers”(您已经尝试过)通常和将此类dict保持排序顺序一样好。但是查找时间大约是O(log N)
(请参阅最后的编辑-看来是错误的!)。
假定在排序列表中添加1个值很快速,尽管它确实需要扩展大小,所以这可能需要O(n)。
在Python列表L
中,在索引i
处插入元素必须(至少)物理移动len(L) - i
指针,这意味着64位指针的字节数是其的8倍。位盒。这就是当数据“大”时sortedcontainer获得巨大成功的地方:它需要物理移动的最坏情况的指针数量受一个独立于len(L)
的常数的限制。在len(L)
进入成千上万之前,很难注意到其中的区别。但是,当len(L)
达到数百万美元时,两者之间的差异就很大。
我会尝试一个折衷方案:使用sortedcontainers SortedList跟踪当前键,并使用Python普通字典作为实际字典。然后:
对于“键添加值”:查看键是否在字典中。非常快。如果是,则无需触摸SortedList。只是改变字典。如果键不在字典中,则需要将其添加到SortedList和字典中。
对于“密钥rem值”:查看密钥是否在字典中。如果不是,我不知道您想做什么,但是您会弄清楚;-)但是,如果它在字典中,请减去该值。如果结果为非零,则操作完成。否则(结果为0),从dict和SortedList中删除键。
注意:不是出于语义原因,我建议使用SortedList而不是SortedSet,而是因为SortedSet需要更多的内存,因此要与有序列表并行维护集合。您没有必要使用它。
除了字典外,您可能真的还需要double-ended ("min max") heap。从您所说的内容中无法猜测-这取决于,例如,您仅想知道“最小和/或最大”的频率,而不是经常要实现整个排序顺序的频率。但是我不知道为速度而构建的Python的最小-最大堆实现-它们是凌乱的代码野兽,很少使用。
编辑
再三考虑,似乎sortedcontainer的SortedDict已经结合了SortedList和普通Python dict(的子类)。例如,在SortedDict中设置值的实现方式如下:
def __setitem__(self,key,value):
if key not in self:
self._list_add(key)
dict.__setitem__(self,value)
因此,只有在字典中没有键时,它才会触摸SortedList。如果您维护自己的
自己动手
这是另一种尝试:
def method3(DATA):
sorted_keys = SortedList()
d = {}
for data in DATA:
if data['mod'] == 'add':
key = data['key']
if key in d:
d[key] += data['val']
else:
d[key] = data['val']
sorted_keys.add(key)
elif data['mod'] == 'rem':
key = data['key']
if key in d:
if d[key] <= data['val']:
del d[key]
sorted_keys.remove(key)
else:
d[key] -= data['val']
else:
pass # Deleting something not there yet
这实现了我最初的建议:使用纯Python字典维护您自己的SortedList对。它具有与使用SortedDict相同的O()
行为,但以恒定因子显示则明显更快。看起来部分原因是因为dict操作现在全部用C编码(SortedDict用Python编码),其余的原因是我们仅对每个data
项目的dict成员资格进行一次测试。例如,在
if key in d:
d[key] += data['val']
当d
是SortedDict时,key in d
会对其进行一次显式测试,但是d.__setitem__()
的实现必须再次对其进行测试,以便可以向其添加key
如果键未知,则为隐藏的SortedList。从更高级别的角度来看,我们已经知道密钥位于if
主体中的dict中,因此可以完全忽略此处的显式SortedList。
您在Arc<Mutex<Params>>
中犯了两个错误:
-
您选择
method1
而不是if key in d.keys():
。没有必要在此处创建按键视图。 -
您可以使用
if key in d:
从列表中删除,而不是使用sorted_keys.remove(key)
来查找索引,然后删除该索引。
修复这些问题,将一些方法存储在局部变量中以进行更短/更快的访问,并使用bisect
可能找到一个键并获取其值(而不是d.get
检查然后查找值) ),我得到这些时间(方法1/2是您的,方法3是Tim的,方法4是我的):
in
Tim要求我将Round 1
method1 7.590627200000004
method2 19.851634099999984
method3 6.093115100000006
method4 5.069753999999989
Round 2
method1 7.857367500000009
method2 19.59779759999998
method3 6.057990299999972
method4 5.0046839999999975
Round 3
method1 7.843560700000012
method2 19.8673627
method3 6.079332300000033
method4 5.073929300000032
更改为randint(0,1000)
:
randint(0,40_000)
和method1 607.2835661000001
method2 26.667593300000135
method3 12.84969140000021
method4 16.68231250000008
(仅较快速的解决方案):
randint(0,400_000)
我的版本:
method3 20.179627500000002
method4 115.39424580000002
完整的基准代码,包括正确性检查:
def method4(DATA):
d = {}
sorted_keys = []
insort = bisect.insort_right
index = bisect.bisect_left
get = d.get
for data in DATA:
if data['mod'] == 'add':
key = data['key']
val = get(key)
if val:
d[key] = val + data['val']
else:
d[key] = data['val']
insort(sorted_keys,key)
elif data['mod'] == 'rem':
key = data['key']
val = get(key)
if val:
if val <= data['val']:
del d[key]
del sorted_keys[index(sorted_keys,key)]
else:
d[key] = val - data['val']
else:
pass # Deleting something not there yet
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。