前言
最近,在做一个简单的排名函数时,我不小心踩到了Python语言的一个坑里。我花了很多时间才找出具体的原因。这是值得记录的。
具体功能要求如下:对500人进行前500名排名,每个人的分数只增加而不减少,当得分相同时,比较列表上的时间戳,得分第一的用户排名。
这个列表之前已经做过很多,一般来说,会先抽象一个TopItem对象,保存用户的点,以及名称、UUID和其他相关信息。然后,使用列表对象top保存500个有序元素,使用dict对象缓存保存从uuid到topitem的映射。由于python对象的reference属性,添加一个额外的dict对象不会增加topitem对象的副本,但都指向同一topitem对象,所以不会有大的内存问题。
因此,整个数据结构可能显示在下面的伪代码中:
class TopItem(object):
def __init__(self, uuid, name, score, t):
self.uuid = uuid
self.name = name
self.score = score
self.t = t
class TopStub(object):
def __init__(self, tops):
self.tops = copy.deepcopy(tops)
self.cache = {}
for item in self.tops:
if item.uuid not in self.cache:
self.cache[item.uuid] = item
小编推荐一个学python的学习qun 740,3222,34
无论你是大牛还是小白,是想转行还是想入行都可以来了解一起进步一起学习!裙内有开发工具,很多干货和技术资料分享!
当玩家播放列表时,首先根据uuid从缓存中查找topitem对象,如果找不到该对象,则直接根据分数以二进制搜索的方式插入;如果找到对象topitem,则更新其分数、名称等信息,然后将其从top列表中删除,然后插入。在二进制搜索中(因为分数只增加或不减少,所以这里有优化)。空间)。
这个排名,计划需要很多能够处理相同的分数(比较时间戳),所以我自然会考虑重写topitem的cmp_u函数,这样你就可以直接使用<>比较topitem,写完代码后就不漂亮了。然后编写以下CMP_uuu函数:
class TopItem(object):
# see above
def __cmp__(self, other):
if self.score != other.score:
return cmp(self.score, other.score)
else:
return cmp(other.t, self.t)
class TopStub(object):
# see above
在测试期间,我做了一个测试功能。我一次随机插入n个用户数据的随机分数,QA通过了测试。没问题。
直到它上线。
大约两个小时后,我发现排名完全混乱,出现了“分阶段下降”的现象,用户信息在排名中反复出现。
经过彻底的调查,发现问题出在“CMP”和“清单”上。远程的从_cmp_u的定义可以看出,如果两个顶项的得分和t相等,那么我们将判断两个顶项是相等的。名单。删除(item)删除与item相等的第一个元素。“CMP”的改写对平等判断有明显影响。也就是说,如果两个顶级项的整数和时间戳相等,那么列表中的顶级项可能是错误的。删除(项)步骤将发生,以便在排名中同时出现多个相同的用户信息。
写一段代码来验证:
def test():
t1 = TopItem('uuid1', 'aaa', 80, 10)
t2 = TopItem('uuid2', 'bbb', 90, 11)
t3 = TopItem('uuid3', 'ccc', 90, 11)
t4 = TopItem('uuid4', 'ddd', 100, 11)
tops = [t4, t3, t2, t1]
print 'tops:', tops
tops.remove(t2)
print 'after remove t2:bbb'
print 'tops:', tops
output:
tops: [(ddd,100,11), (ccc,90,11), (bbb,90,11), (aaa,80,10)]
after remove t2:bbb
tops: [(ddd,100,11), (bbb,90,11), (aaa,80,10)]
果然没错,我们想删除t2(bbb),但是却把和t2“相等”的t3(ccc)删除了。
那么接下来的修正也很简单:
def __cmp__(self, other):
if self.score != other.score:
return cmp(self.score, other.score)
elif self.t != other.t:
return cmp(other.t, self.t)
else:
return cmp(id(self), id(other))
run test again and output:
tops: [(ddd,100,11), (ccc,90,11), (bbb,90,11), (aaa,80,10)]
after remove t2:bbb
tops: [(ddd,100,11), (ccc,90,11), (aaa,80,10)]
总结
记得之前看一些Python的文章时,有人说过,对于双下划线开头的这些函数,除非你确切知道自己在干什么,否则不要覆写这些函数。当时我还不以为然:既然Python提供了这么多有意思的钩子,干嘛不用呢?现在我才明白。
另外,覆写cmp函数时,在最后一个else分支中,比较id是比较稳妥的做法。
最后,Python3已经废弃掉cmp函数,改为提供eq, lt等多个函数,进一步降低使用者犯错的几率。
网友评论