Skip to content
This repository was archived by the owner on Apr 8, 2026. It is now read-only.

Commit d56dab4

Browse files
committed
Leaderboard specific implementations.
1 parent b353bad commit d56dab4

3 files changed

Lines changed: 149 additions & 94 deletions

File tree

leaderboard/competition_ranking_leaderboard.py

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
from .leaderboard import Leaderboard
2-
from redis import StrictRedis, Redis, ConnectionPool
3-
import math
42

53

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

8078
pipeline = self.redis_connection.pipeline()
81-
8279
for member in members:
83-
if self.order == self.ASC:
84-
pipeline.zrank(leaderboard_name, member)
85-
else:
86-
pipeline.zrevrank(leaderboard_name, member)
87-
8880
pipeline.zscore(leaderboard_name, member)
89-
9081
responses = pipeline.execute()
9182

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

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

111-
if ('with_member_data' in options) and (True == options['with_member_data']):
112-
for index, member_data in enumerate(self.members_data_for_in(leaderboard_name, members)):
113-
ranks_for_members[index][self.MEMBER_DATA_KEY] = member_data
102+
if options.get('with_member_data', False):
103+
self._with_member_data(leaderboard_name, members, ranks_for_members)
114104

115105
if 'sort_by' in options:
116-
if self.RANK_KEY == options['sort_by']:
117-
ranks_for_members = sorted(
118-
ranks_for_members,
119-
key=lambda member: member[
120-
self.RANK_KEY])
121-
elif self.SCORE_KEY == options['sort_by']:
122-
ranks_for_members = sorted(
123-
ranks_for_members,
124-
key=lambda member: member[
125-
self.SCORE_KEY])
106+
self._sort_by(ranks_for_members, options['sort_by'])
126107

127108
return ranks_for_members
128109

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

152133
return [self.__up_rank(response) for response in responses]
134+
135+
def _members_from_rank_range_internal(
136+
self, leaderboard_name, start_rank, end_rank, members_only=False, **options):
137+
'''
138+
Format ordered members with score as efficiently as possible.
139+
'''
140+
response = self._range_method(
141+
self.redis_connection,
142+
leaderboard_name,
143+
start_rank,
144+
end_rank,
145+
withscores=not members_only)
146+
147+
if members_only or not response:
148+
return [{self.MEMBER_KEY: member} for member in response]
149+
150+
# Find out where the current rank started using the first two ranks
151+
current_rank = None
152+
current_score = None
153+
current_rank_start = 0
154+
for index, (member, score) in enumerate(response):
155+
if current_score is None:
156+
current_rank = self.rank_for_in(leaderboard_name, member)
157+
current_score = score
158+
elif score != current_score:
159+
next_rank = self.rank_for_in(leaderboard_name, member)
160+
current_rank_start = current_rank - next_rank + index
161+
break
162+
163+
members = []
164+
ranks_for_members = []
165+
for index, (member, score) in enumerate(response):
166+
members.append(member)
167+
if score != current_score:
168+
current_rank += (index - current_rank_start)
169+
current_rank_start = index
170+
current_score = score
171+
172+
member_entry = {
173+
self.MEMBER_KEY: member,
174+
self.RANK_KEY: current_rank,
175+
self.SCORE_KEY: score,
176+
}
177+
ranks_for_members.append(member_entry)
178+
179+
if options.get('with_member_data', False):
180+
self._with_member_data(leaderboard_name, members, ranks_for_members)
181+
182+
if 'sort_by' in options:
183+
self._sort_by(ranks_for_members, options['sort_by'])
184+
185+
return ranks_for_members

leaderboard/leaderboard.py

Lines changed: 47 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Leaderboard(object):
3232
RANK_KEY = 'rank'
3333

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

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

@@ -778,7 +778,7 @@ def leaders(self, current_page, **options):
778778
'''
779779
return self.leaders_in(self.leaderboard_name, current_page, **options)
780780

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

801801
ending_offset = (starting_offset + page_size) - 1
802802

803-
response = self._range_method(
804-
self.redis_connection,
803+
return self._members_from_rank_range_internal(
805804
leaderboard_name,
806805
int(starting_offset),
807806
int(ending_offset),
808-
withscores=not members_only)
809-
810-
if members_only:
811-
return [{self.MEMBER_KEY: member} for member in response]
812-
813-
members = []
814-
ranks_for_members = []
815-
for i, (member, score) in enumerate(response):
816-
members.append(member)
817-
ranks_for_members.append({
818-
self.MEMBER_KEY: member,
819-
self.RANK_KEY: starting_offset + i + 1,
820-
self.SCORE_KEY: score,
821-
})
822-
823-
if options.get('with_member_data', False):
824-
self._with_member_data(leaderboard_name, members, ranks_for_members)
825-
826-
if 'sort_by' in options:
827-
self._sort_by(ranks_for_members, options['sort_by'])
828-
829-
return ranks_for_members
807+
**options)
830808

831809
def all_leaders(self, **options):
832810
'''
@@ -845,10 +823,8 @@ def all_leaders_from(self, leaderboard_name, **options):
845823
@param options [Hash] Options to be used when retrieving the leaders from the named leaderboard.
846824
@return the named leaderboard.
847825
'''
848-
raw_leader_data = self._range_method(
849-
self.redis_connection, leaderboard_name, 0, -1, withscores=False)
850-
return self._parse_raw_members(
851-
leaderboard_name, raw_leader_data, **options)
826+
return self._members_from_rank_range_internal(
827+
leaderboard_name, 0, -1, **options)
852828

853829
def members_from_score_range(
854830
self, minimum_score, maximum_score, **options):
@@ -919,22 +895,8 @@ def members_from_rank_range_in(
919895
if ending_rank > self.total_members_in(leaderboard_name):
920896
ending_rank = self.total_members_in(leaderboard_name) - 1
921897

922-
raw_leader_data = []
923-
if self.order == self.DESC:
924-
raw_leader_data = self.redis_connection.zrevrange(
925-
leaderboard_name,
926-
starting_rank,
927-
ending_rank,
928-
withscores=False)
929-
else:
930-
raw_leader_data = self.redis_connection.zrange(
931-
leaderboard_name,
932-
starting_rank,
933-
ending_rank,
934-
withscores=False)
935-
936-
return self._parse_raw_members(
937-
leaderboard_name, raw_leader_data, **options)
898+
return self._members_from_rank_range_internal(
899+
leaderboard_name, starting_rank, ending_rank, **options)
938900

939901
def top(self, number, **options):
940902
'''
@@ -1031,14 +993,11 @@ def around_me_in(self, leaderboard_name, member, **options):
1031993

1032994
ending_offset = (starting_offset + page_size) - 1
1033995

1034-
raw_leader_data = self._range_method(
1035-
self.redis_connection,
996+
return self._members_from_rank_range_internal(
1036997
leaderboard_name,
1037998
int(starting_offset),
1038999
int(ending_offset),
1039-
withscores=False)
1040-
return self._parse_raw_members(
1041-
leaderboard_name, raw_leader_data, **options)
1000+
**options)
10421001

10431002
def ranked_in_list(self, members, **options):
10441003
'''
@@ -1177,3 +1136,39 @@ def _parse_raw_members(
11771136
return self.ranked_in_list_in(leaderboard_name, members, **options)
11781137
else:
11791138
return []
1139+
1140+
def _members_from_rank_range_internal(
1141+
self, leaderboard_name, start_rank, end_rank, members_only=False, **options):
1142+
'''
1143+
Format ordered members with score as efficiently as possible.
1144+
'''
1145+
response = self._range_method(
1146+
self.redis_connection,
1147+
leaderboard_name,
1148+
start_rank,
1149+
end_rank,
1150+
withscores=not members_only)
1151+
1152+
if members_only or not response:
1153+
return [{self.MEMBER_KEY: member} for member in response]
1154+
1155+
current_rank = start_rank
1156+
members = []
1157+
ranks_for_members = []
1158+
for index, (member, score) in enumerate(response):
1159+
members.append(member)
1160+
current_rank += 1
1161+
member_entry = {
1162+
self.MEMBER_KEY: member,
1163+
self.RANK_KEY: current_rank,
1164+
self.SCORE_KEY: score,
1165+
}
1166+
ranks_for_members.append(member_entry)
1167+
1168+
if options.get('with_member_data', False):
1169+
self._with_member_data(leaderboard_name, members, ranks_for_members)
1170+
1171+
if 'sort_by' in options:
1172+
self._sort_by(ranks_for_members, options['sort_by'])
1173+
1174+
return ranks_for_members

leaderboard/tie_ranking_leaderboard.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from .leaderboard import Leaderboard
22
from .leaderboard import grouper
3-
from redis import StrictRedis, Redis, ConnectionPool
4-
import math
3+
from redis import Redis
54

65

76
class TieRankingLeaderboard(Leaderboard):
@@ -120,7 +119,7 @@ def rank_member_across(
120119
@param member_data [String] Optional member data.
121120
'''
122121
for leaderboard_name in leaderboards:
123-
self.rank_member_in(leaderboard, member, score, member_data)
122+
self.rank_member_in(leaderboard_name, member, score, member_data)
124123

125124
def rank_members_in(self, leaderboard_name, members_and_scores):
126125
'''
@@ -271,24 +270,11 @@ def ranked_in_list_in(self, leaderboard_name, members, **options):
271270

272271
ranks_for_members.append(data)
273272

274-
if ('with_member_data' in options) and (True == options['with_member_data']):
275-
for index, member_data in enumerate(self.members_data_for_in(leaderboard_name, members)):
276-
try:
277-
ranks_for_members[index][self.MEMBER_DATA_KEY] = member_data
278-
except:
279-
pass
273+
if options.get('with_member_data', False):
274+
self._with_member_data(leaderboard_name, members, ranks_for_members)
280275

281276
if 'sort_by' in options:
282-
if self.RANK_KEY == options['sort_by']:
283-
ranks_for_members = sorted(
284-
ranks_for_members,
285-
key=lambda member: member[
286-
self.RANK_KEY])
287-
elif self.SCORE_KEY == options['sort_by']:
288-
ranks_for_members = sorted(
289-
ranks_for_members,
290-
key=lambda member: member[
291-
self.SCORE_KEY])
277+
self._sort_by(ranks_for_members, options['sort_by'])
292278

293279
return ranks_for_members
294280

@@ -300,3 +286,44 @@ def _ties_leaderboard_key(self, leaderboard_name):
300286
@return a key in the form of +leaderboard_name:ties_namespace+
301287
'''
302288
return '%s:%s' % (leaderboard_name, self.ties_namespace)
289+
290+
def _members_from_rank_range_internal(
291+
self, leaderboard_name, start_rank, end_rank, members_only=False, **options):
292+
'''
293+
Format ordered members with score as efficiently as possible.
294+
'''
295+
response = self._range_method(
296+
self.redis_connection,
297+
leaderboard_name,
298+
start_rank,
299+
end_rank,
300+
withscores=not members_only)
301+
302+
if members_only or not response:
303+
return [{self.MEMBER_KEY: member} for member in response]
304+
305+
current_member, current_score = response[0]
306+
current_rank = self.rank_for_in(leaderboard_name, current_member)
307+
current_score = response[0][1]
308+
members = []
309+
ranks_for_members = []
310+
for index, (member, score) in enumerate(response):
311+
if score != current_score:
312+
current_rank += 1
313+
current_score = score
314+
315+
members.append(member)
316+
member_entry = {
317+
self.MEMBER_KEY: member,
318+
self.RANK_KEY: current_rank,
319+
self.SCORE_KEY: score,
320+
}
321+
ranks_for_members.append(member_entry)
322+
323+
if options.get('with_member_data', False):
324+
self._with_member_data(leaderboard_name, members, ranks_for_members)
325+
326+
if 'sort_by' in options:
327+
self._sort_by(ranks_for_members, options['sort_by'])
328+
329+
return ranks_for_members

0 commit comments

Comments
 (0)