背景介紹
排行榜通常是游戲中為了激發玩家的一種策略,那么對於開發人員來說如何完成一個排行榜的設計呢?如果這個排行榜是動態的如何才能高效的對比出結果呢?如果排行榜實時性較高如何給用戶展示出用戶是進步了還是退步了呢?帶着這些問題我們一步步開始探究。可能我實現的方式並不高效期待你能夠提出寶貴的意見。所有代碼可以到這里github
排名
假設我們要實現排行榜功能肯定避免不了排序,那么是怎么排序比較好呢?當用戶查詢自己的戰績時再去查數據庫?那樣就會使用戶體驗變差了!如果用戶量非常大可能排序會讓用戶不耐煩。好那么我們就使用后台定時去計算排名,這樣就可以避免用戶體驗差了,我們可以選擇用redis
緩存結果。好開始寫demo
:
a=[
{
'score': 123,
'name': 'frank'
},
{
'name': 'jack',
'score': 44
},
{
'name': 'susan',
'score': 188
},
{
'name': 'lisa',
'score': 99
}
]
b=[
{
'score': 12,
'name': 'frank'
},
{
'name': 'jack',
'score': 44
},
{
'name': 'susan',
'score': 223
},
{
'name': 'lisa',
'score': 99
}
]
可以看到我們的數據目前是非常簡單的,只有用戶名和戰績分數兩個項。下面我們開始給他排序,注意我們需要根據score
來排序,使用python
非常容易實現。
sorted(a, lambda x,y: cmp(x.get('score'), y.get('score')))
可以看到我們目前的鏈表是按照score
來排序了!那么如何才能知道用戶是上升了幾名還是下降了幾名呢?這個很容易想到與上次數據對比即可,這樣我們就有下面的函數了。
def trend_pk(old_list, new_list):
return a_new_list
如果要實現兩個列表的對比難免要一個循環語句,這時候我們是循環new_list
還是old_list
呢?顯然我們知道old_list
可以為空,也就是第一次執行排名對比的時候。好那么我們就可以選擇new_list
來循環了。
def trend_pk(old_list, new_list):
for i in new_list:
pass
return new_list
有了循環語句我們自然需要改變些什么,設想如果一個用戶出現在排行榜上了他肯定應該得到些什么,也許是獎品也許只是一個名次等。好那么我們需要判斷這個用戶是不是第一次上榜,所以自然想到去看看歷史列表有沒有他。
def trend_pk(old_list, new_list):
for i in new_uid_list:
if i in old_uid_list:
pass
else:
pass
return new_list
回頭看看數據結構,我們發現i
其實是這樣的。
{
'score': 123,
'name': 'frank'
}
這個可不是用戶的唯一標示,我們需要的是用戶名或者身份證id
等。所以我們需要提取一下用戶的唯一標示也就是這里的用戶名。
def trend_pk(old_list, new_list):
old_uid_list = [i.get('name') for i in old_list]
new_uid_list = [i.get('name') for i in new_list]
for i in new_list:
if i in old_uid_list:
pass
else:
pass
return new_list
現在我們有了一個uid_list
列表,我可以很輕松的得到用戶名,但是我們希望這些用戶是已經按照分數排好序的。我們可以這樣做:
def trend_pk(old_list, new_list):
# You should read both list from redis
old_list = sorted(old_list, lambda x,y: cmp(x.get("score"), y.get("score")))
new_list = sorted(new_list, lambda x,y: cmp(x.get("score"), y.get("score")))
old_uid_list = [i.get('name') for i in old_list]
new_uid_list = [i.get('name') for i in new_list]
for i in new_uid_list:
if i in old_uid_list:
pass
else:
pass
return new_list
上面的代碼的if
語句只判斷uid
是否在列表里面即可,此時我們需要做的就是對uid
是否有歷史記錄分別做不同處理。
def trend_pk(old_list, new_list):
# You should read both list from redis
old_list = sorted(old_list, lambda x,y: cmp(x.get("score"), y.get("score")))
new_list = sorted(new_list, lambda x,y: cmp(x.get("score"), y.get("score")))
old_uid_list = [i.get('name') for i in old_list]
new_uid_list = [i.get('name') for i in new_list]
for i in new_uid_list:
index = new_uid_list.index(i)
if i in old_uid_list:
old_index = old_uid_list.index(i)
new_list[index].update({'trend': trend_pk_tool(old_index, index)})
else:
new_list[index].update({'trend':'1'})
return new_list
首先我通過list
自帶的index
函數獲取到當前列表的位置,然后如果這個用戶有歷史記錄我就去獲取歷史記錄位置,然后通過一個函數對比這兩個位置。不着急待會告訴大家這就對比函數怎么寫的。繼續看else
部分表示如果這個用戶沒有歷史記錄,說明他第一次有記錄那么這就是有進步。如果用戶還關心自己當前到底排在第幾名怎么辦呢?
def trend_pk(old_list, new_list):
# You should read both list from redis
old_list = sorted(old_list, lambda x,y: cmp(x.get("score"), y.get("score")))
new_list = sorted(new_list, lambda x,y: cmp(x.get("score"), y.get("score")))
old_uid_list = [i.get('name') for i in old_list]
new_uid_list = [i.get('name') for i in new_list]
for i in new_uid_list:
index = new_uid_list.index(i)
if i in old_uid_list:
old_index = old_uid_list.index(i)
new_list[index].update({'trend': trend_pk_tool(old_index, index)})
else:
new_list[index].update({'trend':'1'})
new_list[index].update({'rank':str(index)})
return new_list
可以看到在return
之前我們增加了一個rank
字段。下面來看看我的trend_pk_tool
是什么工具函數吧。
def trend_pk_tool(o, n):
if o > n:
return '1'
elif o == n:
return '0'
else:
return '-1'
簡單吧!根據不同的對比結果返回不同的值,或許你還有跟簡單的方法來寫這個函數哦!想想看~~
快速獲取個人戰績
為了快速獲取增加的戰況,首先肯定知道自己的用戶名才能獲得自己的戰績。好我們可以用字典的方式,那么剛才的列表要怎么變成列表呢?
def map_uid_to_score_info(score_list):
redis_cache = {}
for i in score_list:
uid = i.get('name')
redis_cache.update({
uid:{
'score':i.get('score'),
'trend':i.get('trend'),
'rank':i.get('rank')
}
})
return redis_cache
好了!這次代碼給得非常干脆,現在我們來看看運行結果吧!
if __name__ == '__main__':
ret = trend_pk(a, b)
redis_cache = map_uid_to_score_info(ret)
print redis_cache
拿着我的demo
給產品經理確認這數據是不是他想要的,他看了看說不對吧!這個成績低的怎么排名在前面呢?這個問題怎么很簡單只需要在sorted
時指定一個參數即可。看看下面代碼:
old_list = sorted(old_list, lambda x,y: cmp(x.get("score"), y.get("score")), reverse=True)
new_list = sorted(new_list, lambda x,y: cmp(x.get("score"), y.get("score")), reverse=True)
當榜單出現並列名次時如何解決
今天跟產品討論了一個問題,如果出現並列第一的獎品該如何分配?榜單該如何排序?對於產品可能會想通過增加條款或者其他方式解決。而我首先想到的是按照第二條件進行再次排序。所以我想了想如何通過sorted
進行多項匹配排序。
sample = [('d', 2), ('a', 4), ('b', 3), ('c', 2)]
print sorted(sample, key=lambda x:(x[1], x[0]))
運行結果是先按照數字排序,再按照字母排序的。
總結
那么在實戰項目中我們需要注意些什么呢?首先我們需要用數據庫,mysql
查詢到最新的記錄,排名完成后保存到redis
中。現實場景中可還可能對不同的排名派發獎品,如何將這些獎品進行兌換也是需要考慮的。