Skip to content

Commit

Permalink
Leaderboard specific implementations.
Browse files Browse the repository at this point in the history
  • Loading branch information
GraylinKim committed May 22, 2019
1 parent b353bad commit d56dab4
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 94 deletions.
79 changes: 56 additions & 23 deletions leaderboard/competition_ranking_leaderboard.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from .leaderboard import Leaderboard
from redis import StrictRedis, Redis, ConnectionPool
import math


class CompetitionRankingLeaderboard(Leaderboard):
Expand Down Expand Up @@ -78,22 +76,15 @@ def ranked_in_list_in(self, leaderboard_name, members, **options):
scores = []

pipeline = self.redis_connection.pipeline()

for member in members:
if self.order == self.ASC:
pipeline.zrank(leaderboard_name, member)
else:
pipeline.zrevrank(leaderboard_name, member)

pipeline.zscore(leaderboard_name, member)

responses = pipeline.execute()

for index, member in enumerate(members):
data = {}
data[self.MEMBER_KEY] = member

score = responses[index * 2 + 1]
score = responses[index]
if score is not None:
score = float(score)
else:
Expand All @@ -108,21 +99,11 @@ def ranked_in_list_in(self, leaderboard_name, members, **options):
for index, rank in enumerate(self.__rankings_for_members_having_scores_in(leaderboard_name, members, scores)):
ranks_for_members[index][self.RANK_KEY] = rank

if ('with_member_data' in options) and (True == options['with_member_data']):
for index, member_data in enumerate(self.members_data_for_in(leaderboard_name, members)):
ranks_for_members[index][self.MEMBER_DATA_KEY] = member_data
if options.get('with_member_data', False):
self._with_member_data(leaderboard_name, members, ranks_for_members)

if 'sort_by' in options:
if self.RANK_KEY == options['sort_by']:
ranks_for_members = sorted(
ranks_for_members,
key=lambda member: member[
self.RANK_KEY])
elif self.SCORE_KEY == options['sort_by']:
ranks_for_members = sorted(
ranks_for_members,
key=lambda member: member[
self.SCORE_KEY])
self._sort_by(ranks_for_members, options['sort_by'])

return ranks_for_members

Expand Down Expand Up @@ -150,3 +131,55 @@ def __rankings_for_members_having_scores_in(self, leaderboard_name, members, sco
responses = pipeline.execute()

return [self.__up_rank(response) for response in responses]

def _members_from_rank_range_internal(
self, leaderboard_name, start_rank, end_rank, members_only=False, **options):
'''
Format ordered members with score as efficiently as possible.
'''
response = self._range_method(
self.redis_connection,
leaderboard_name,
start_rank,
end_rank,
withscores=not members_only)

if members_only or not response:
return [{self.MEMBER_KEY: member} for member in response]

# Find out where the current rank started using the first two ranks
current_rank = None
current_score = None
current_rank_start = 0
for index, (member, score) in enumerate(response):
if current_score is None:
current_rank = self.rank_for_in(leaderboard_name, member)
current_score = score
elif score != current_score:
next_rank = self.rank_for_in(leaderboard_name, member)
current_rank_start = current_rank - next_rank + index
break

members = []
ranks_for_members = []
for index, (member, score) in enumerate(response):
members.append(member)
if score != current_score:
current_rank += (index - current_rank_start)
current_rank_start = index
current_score = score

member_entry = {
self.MEMBER_KEY: member,
self.RANK_KEY: current_rank,
self.SCORE_KEY: score,
}
ranks_for_members.append(member_entry)

if options.get('with_member_data', False):
self._with_member_data(leaderboard_name, members, ranks_for_members)

if 'sort_by' in options:
self._sort_by(ranks_for_members, options['sort_by'])

return ranks_for_members
99 changes: 47 additions & 52 deletions leaderboard/leaderboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Leaderboard(object):
RANK_KEY = 'rank'

@classmethod
def pool(self, host, port, db, pools={}, **options):
def pool(cls, host, port, db, pools={}, **options):
'''
Fetch a redis connection pool for the unique combination of host
and port. Will create a new one if there isn't one already.
Expand Down Expand Up @@ -74,7 +74,7 @@ def __init__(self, leaderboard_name, **options):
self.DEFAULT_GLOBAL_MEMBER_DATA)

self.order = self.options.pop('order', self.DESC).lower()
if not self.order in [self.ASC, self.DESC]:
if self.order not in [self.ASC, self.DESC]:
raise ValueError(
"%s is not one of [%s]" % (self.order, ",".join([self.ASC, self.DESC])))

Expand Down Expand Up @@ -778,7 +778,7 @@ def leaders(self, current_page, **options):
'''
return self.leaders_in(self.leaderboard_name, current_page, **options)

def leaders_in(self, leaderboard_name, current_page, members_only=False, **options):
def leaders_in(self, leaderboard_name, current_page, **options):
'''
Retrieve a page of leaders from the named leaderboard.
Expand All @@ -800,33 +800,11 @@ def leaders_in(self, leaderboard_name, current_page, members_only=False, **optio

ending_offset = (starting_offset + page_size) - 1

response = self._range_method(
self.redis_connection,
return self._members_from_rank_range_internal(
leaderboard_name,
int(starting_offset),
int(ending_offset),
withscores=not members_only)

if members_only:
return [{self.MEMBER_KEY: member} for member in response]

members = []
ranks_for_members = []
for i, (member, score) in enumerate(response):
members.append(member)
ranks_for_members.append({
self.MEMBER_KEY: member,
self.RANK_KEY: starting_offset + i + 1,
self.SCORE_KEY: score,
})

if options.get('with_member_data', False):
self._with_member_data(leaderboard_name, members, ranks_for_members)

if 'sort_by' in options:
self._sort_by(ranks_for_members, options['sort_by'])

return ranks_for_members
**options)

def all_leaders(self, **options):
'''
Expand All @@ -845,10 +823,8 @@ def all_leaders_from(self, leaderboard_name, **options):
@param options [Hash] Options to be used when retrieving the leaders from the named leaderboard.
@return the named leaderboard.
'''
raw_leader_data = self._range_method(
self.redis_connection, leaderboard_name, 0, -1, withscores=False)
return self._parse_raw_members(
leaderboard_name, raw_leader_data, **options)
return self._members_from_rank_range_internal(
leaderboard_name, 0, -1, **options)

def members_from_score_range(
self, minimum_score, maximum_score, **options):
Expand Down Expand Up @@ -919,22 +895,8 @@ def members_from_rank_range_in(
if ending_rank > self.total_members_in(leaderboard_name):
ending_rank = self.total_members_in(leaderboard_name) - 1

raw_leader_data = []
if self.order == self.DESC:
raw_leader_data = self.redis_connection.zrevrange(
leaderboard_name,
starting_rank,
ending_rank,
withscores=False)
else:
raw_leader_data = self.redis_connection.zrange(
leaderboard_name,
starting_rank,
ending_rank,
withscores=False)

return self._parse_raw_members(
leaderboard_name, raw_leader_data, **options)
return self._members_from_rank_range_internal(
leaderboard_name, starting_rank, ending_rank, **options)

def top(self, number, **options):
'''
Expand Down Expand Up @@ -1031,14 +993,11 @@ def around_me_in(self, leaderboard_name, member, **options):

ending_offset = (starting_offset + page_size) - 1

raw_leader_data = self._range_method(
self.redis_connection,
return self._members_from_rank_range_internal(
leaderboard_name,
int(starting_offset),
int(ending_offset),
withscores=False)
return self._parse_raw_members(
leaderboard_name, raw_leader_data, **options)
**options)

def ranked_in_list(self, members, **options):
'''
Expand Down Expand Up @@ -1177,3 +1136,39 @@ def _parse_raw_members(
return self.ranked_in_list_in(leaderboard_name, members, **options)
else:
return []

def _members_from_rank_range_internal(
self, leaderboard_name, start_rank, end_rank, members_only=False, **options):
'''
Format ordered members with score as efficiently as possible.
'''
response = self._range_method(
self.redis_connection,
leaderboard_name,
start_rank,
end_rank,
withscores=not members_only)

if members_only or not response:
return [{self.MEMBER_KEY: member} for member in response]

current_rank = start_rank
members = []
ranks_for_members = []
for index, (member, score) in enumerate(response):
members.append(member)
current_rank += 1
member_entry = {
self.MEMBER_KEY: member,
self.RANK_KEY: current_rank,
self.SCORE_KEY: score,
}
ranks_for_members.append(member_entry)

if options.get('with_member_data', False):
self._with_member_data(leaderboard_name, members, ranks_for_members)

if 'sort_by' in options:
self._sort_by(ranks_for_members, options['sort_by'])

return ranks_for_members
65 changes: 46 additions & 19 deletions leaderboard/tie_ranking_leaderboard.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from .leaderboard import Leaderboard
from .leaderboard import grouper
from redis import StrictRedis, Redis, ConnectionPool
import math
from redis import Redis


class TieRankingLeaderboard(Leaderboard):
Expand Down Expand Up @@ -120,7 +119,7 @@ def rank_member_across(
@param member_data [String] Optional member data.
'''
for leaderboard_name in leaderboards:
self.rank_member_in(leaderboard, member, score, member_data)
self.rank_member_in(leaderboard_name, member, score, member_data)

def rank_members_in(self, leaderboard_name, members_and_scores):
'''
Expand Down Expand Up @@ -271,24 +270,11 @@ def ranked_in_list_in(self, leaderboard_name, members, **options):

ranks_for_members.append(data)

if ('with_member_data' in options) and (True == options['with_member_data']):
for index, member_data in enumerate(self.members_data_for_in(leaderboard_name, members)):
try:
ranks_for_members[index][self.MEMBER_DATA_KEY] = member_data
except:
pass
if options.get('with_member_data', False):
self._with_member_data(leaderboard_name, members, ranks_for_members)

if 'sort_by' in options:
if self.RANK_KEY == options['sort_by']:
ranks_for_members = sorted(
ranks_for_members,
key=lambda member: member[
self.RANK_KEY])
elif self.SCORE_KEY == options['sort_by']:
ranks_for_members = sorted(
ranks_for_members,
key=lambda member: member[
self.SCORE_KEY])
self._sort_by(ranks_for_members, options['sort_by'])

return ranks_for_members

Expand All @@ -300,3 +286,44 @@ def _ties_leaderboard_key(self, leaderboard_name):
@return a key in the form of +leaderboard_name:ties_namespace+
'''
return '%s:%s' % (leaderboard_name, self.ties_namespace)

def _members_from_rank_range_internal(
self, leaderboard_name, start_rank, end_rank, members_only=False, **options):
'''
Format ordered members with score as efficiently as possible.
'''
response = self._range_method(
self.redis_connection,
leaderboard_name,
start_rank,
end_rank,
withscores=not members_only)

if members_only or not response:
return [{self.MEMBER_KEY: member} for member in response]

current_member, current_score = response[0]
current_rank = self.rank_for_in(leaderboard_name, current_member)
current_score = response[0][1]
members = []
ranks_for_members = []
for index, (member, score) in enumerate(response):
if score != current_score:
current_rank += 1
current_score = score

members.append(member)
member_entry = {
self.MEMBER_KEY: member,
self.RANK_KEY: current_rank,
self.SCORE_KEY: score,
}
ranks_for_members.append(member_entry)

if options.get('with_member_data', False):
self._with_member_data(leaderboard_name, members, ranks_for_members)

if 'sort_by' in options:
self._sort_by(ranks_for_members, options['sort_by'])

return ranks_for_members

0 comments on commit d56dab4

Please sign in to comment.