Created
March 21, 2014 11:35
-
-
Save cooldaemon/9684339 to your computer and use it in GitHub Desktop.
Redis Sorted Set を用いた, 同点を加味したランキング処理を Python で実装する ref: http://qiita.com/cooldaemon/items/eea31a1733f44a914401
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
>>> from redis import Redis | |
>>> from ranking import Ranking | |
>>> | |
>>> ranking = Ranking(Redis(), 'event1') | |
>>> | |
>>> ranking.push('p1', 200) | |
>>> ranking.push('p2', 100) | |
>>> ranking.push('p3', 300) | |
>>> ranking.push('p1', 1000) | |
>>> ranking.push('p4', 1000) | |
>>> | |
>>> l1 = ranking.gen_list() # ['p4', 'p1', 'p3', 'p2'] | |
>>> l1[2:] # ['p3', 'p2'] | |
>>> | |
>>> import Player # e.g. Django Model | |
>>> def wrapper(id): | |
return Player.objects.get(pk=id) | |
>>> l2 = ranking.gen_list(wrapper) # [Player('p4'), Player('p1'), Player('p3'), Player('p2')] | |
>>> l2[2:] # [Player('p3'), Player('p2')] | |
>>> | |
>>> [ranking.get_rank(player_id) for player_id in l1] # [1, 1, 2, 3] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from datetime import timedelta | |
from .rankinglist import RankingList | |
_default_expire = int(timedelta(weeks=2).total_seconds()) | |
class Ranking(object): | |
def __init__(self, client, key, expire=_default_expire): | |
""" | |
クラスの初期化を行う. | |
:param object client: Redis のクライアント. | |
:param string key: ランキングの識別子. | |
:param int expire: ランキングの有効期限. 省略すると二週間. | |
""" | |
self._r = client | |
self._key = key | |
self._expire = expire | |
def push(self, unique_id, value): | |
""" | |
ランキングの更新を行う. | |
:param string unique_id: ランキングする ID. | |
:param string value: ランキングのソース値(ポイント等) | |
""" | |
self._r.zadd(self._key, long(value), unique_id) | |
self.touch() | |
def get_rank(self, unique_id): | |
""" | |
順位の取得を行う. | |
:param string unique_id: ランキングする ID. | |
:return: 順位 | |
""" | |
value = self._r.zscore(self._key, unique_id) | |
if value is None: | |
return None | |
return self._r.zcount(self._key, '({}'.format(int(value)), 'inf') + 1 | |
def get_range(self, start, end): | |
""" | |
ランキングの範囲取得を行う. | |
:param int start: 添字の開始位置. | |
:param int end: 添字の終了位置. start=0 and end=0 で, 先頭の一件を取得. | |
:return: ['push() で指定した unique_id', ...] | |
""" | |
result = self._r.zrevrange(self._key, start, end) | |
self.touch() | |
return result | |
def get_count(self): | |
""" | |
件数の取得を行う. | |
:return: 件数 | |
""" | |
return self._r.zcard(self._key) | |
def touch(self): | |
""" | |
ランキングの有効期限を延長する. | |
push() と get_rank() でも実行される. | |
""" | |
self._r.expire(self._key, self._expire) | |
def clean(self): | |
""" | |
ランキングを削除する. | |
""" | |
self._r.delete(self._key) | |
def gen_list(self, wrapper=None): | |
""" | |
ランキングリストを取得する. | |
:param function wrapper: 要素を包む関数 | |
:return: RankingList オブジェクト | |
RankingList は, ある程度 List として振る舞う. | |
Django の Paginator に渡す際などに使用する. | |
""" | |
return RankingList(self, wrapper) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RankingList(object): | |
def __init__(self, rank, wrapper=None): | |
self._rank = rank | |
self._wrapper = wrapper | |
def __getitem__(self, k): | |
if isinstance(k, slice): | |
start = k.start if k.start else 0 | |
end = k.stop - 1 if k.stop else self.__len__() - 1 | |
step = k.step | |
unique_ids = self._rank.get_range(start, end) | |
if step: | |
unique_ids = unique_ids[::step] | |
return [self._wrap(unique_id) for unique_id in unique_ids] | |
else: | |
if self.__len__() <= k: | |
raise IndexError('list index out of range') | |
unique_ids = self._rank.get_range(k, k) | |
return self._wrap(unique_ids[0]) | |
def _wrap(self, unique_id): | |
return self._wrapper(unique_id) if self._wrapper else unique_id | |
def __len__(self): | |
return self._rank.get_count() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment