docs
0
# Sorted Set Operations
1
2
Redis sorted set data type operations providing ordered collections of unique strings with associated scores. Sorted sets combine the uniqueness of sets with the ordering capabilities of lists, making them perfect for leaderboards, priority queues, time-series data, and ranked collections. Each member has an associated score used for ordering.
3
4
## Capabilities
5
6
### Basic Sorted Set Operations
7
8
Core sorted set manipulation functions for adding, removing, and querying members with scores.
9
10
```python { .api }
11
def zadd(
12
self,
13
name: KeyT,
14
mapping: Optional[Mapping[AnyKeyT, EncodableT]] = None,
15
nx: bool = False,
16
xx: bool = False,
17
ch: bool = False,
18
incr: bool = False,
19
gt: bool = False,
20
lt: bool = False,
21
**kwargs: EncodableT
22
) -> ResponseT: ...
23
24
def zrem(self, name: KeyT, *values: EncodableT) -> ResponseT: ...
25
26
def zcard(self, name: KeyT) -> ResponseT: ...
27
28
def zcount(self, name: KeyT, min: ZScoreT, max: ZScoreT) -> ResponseT: ...
29
30
def zscore(self, name: KeyT, value: EncodableT) -> Optional[float]: ...
31
32
def zmscore(self, key: KeyT, members: Iterable[EncodableT]) -> List[Optional[float]]: ...
33
34
def zrank(self, name: KeyT, value: EncodableT) -> Optional[int]: ...
35
36
def zrevrank(self, name: KeyT, value: EncodableT) -> Optional[int]: ...
37
38
def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: ...
39
```
40
41
### Range Operations
42
43
Functions for retrieving members by rank or score ranges with flexible ordering options.
44
45
```python { .api }
46
def zrange(
47
self,
48
name: KeyT,
49
start: int,
50
end: int,
51
desc: bool = False,
52
withscores: bool = False,
53
score_cast_func: Optional[Callable] = float,
54
byscore: bool = False,
55
bylex: bool = False,
56
offset: Optional[int] = None,
57
num: Optional[int] = None
58
) -> Union[List[bytes], List[Tuple[bytes, float]]]: ...
59
60
def zrevrange(
61
self,
62
name: KeyT,
63
start: int,
64
end: int,
65
withscores: bool = False,
66
score_cast_func: Optional[Callable] = float
67
) -> Union[List[bytes], List[Tuple[bytes, float]]]: ...
68
69
def zrangebyscore(
70
self,
71
name: KeyT,
72
min: ZScoreT,
73
max: ZScoreT,
74
start: Optional[int] = None,
75
num: Optional[int] = None,
76
withscores: bool = False,
77
score_cast_func: Optional[Callable] = float
78
) -> Union[List[bytes], List[Tuple[bytes, float]]]: ...
79
80
def zrevrangebyscore(
81
self,
82
name: KeyT,
83
max: ZScoreT,
84
min: ZScoreT,
85
start: Optional[int] = None,
86
num: Optional[int] = None,
87
withscores: bool = False,
88
score_cast_func: Optional[Callable] = float
89
) -> Union[List[bytes], List[Tuple[bytes, float]]]: ...
90
91
def zrangebylex(
92
self,
93
name: KeyT,
94
min: EncodableT,
95
max: EncodableT,
96
start: Optional[int] = None,
97
num: Optional[int] = None
98
) -> List[bytes]: ...
99
100
def zrevrangebylex(
101
self,
102
name: KeyT,
103
max: EncodableT,
104
min: EncodableT,
105
start: Optional[int] = None,
106
num: Optional[int] = None
107
) -> List[bytes]: ...
108
```
109
110
### Range Removal Operations
111
112
Functions for removing members by rank or score ranges.
113
114
```python { .api }
115
def zremrangebyrank(self, name: KeyT, start: int, end: int) -> ResponseT: ...
116
117
def zremrangebyscore(self, name: KeyT, min: ZScoreT, max: ZScoreT) -> ResponseT: ...
118
119
def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: ...
120
```
121
122
### Pop Operations
123
124
Functions for atomically removing and returning members with highest or lowest scores.
125
126
```python { .api }
127
def zpopmin(self, name: KeyT, count: Optional[int] = None) -> Union[List[Union[bytes, float]], List[Tuple[bytes, float]]]: ...
128
129
def zpopmax(self, name: KeyT, count: Optional[int] = None) -> Union[List[Union[bytes, float]], List[Tuple[bytes, float]]]: ...
130
131
def bzpopmin(
132
self,
133
keys: Union[KeyT, Iterable[KeyT]],
134
timeout: float = 0
135
) -> Optional[Tuple[bytes, bytes, float]]: ...
136
137
def bzpopmax(
138
self,
139
keys: Union[KeyT, Iterable[KeyT]],
140
timeout: float = 0
141
) -> Optional[Tuple[bytes, bytes, float]]: ...
142
```
143
144
### Set Algebra Operations
145
146
Functions for performing operations between multiple sorted sets.
147
148
```python { .api }
149
def zinter(
150
self,
151
keys: Iterable[KeyT],
152
aggregate: Optional[Literal["SUM", "MIN", "MAX"]] = None,
153
withscores: bool = False
154
) -> Union[List[bytes], List[Tuple[bytes, float]]]: ...
155
156
def zinterstore(
157
self,
158
dest: KeyT,
159
keys: Union[Mapping[AnyKeyT, float], Iterable[KeyT]],
160
aggregate: Optional[Literal["SUM", "MIN", "MAX"]] = None
161
) -> ResponseT: ...
162
163
def zunionstore(
164
self,
165
dest: KeyT,
166
keys: Union[Mapping[AnyKeyT, float], Iterable[KeyT]],
167
aggregate: Optional[Literal["SUM", "MIN", "MAX"]] = None
168
) -> ResponseT: ...
169
170
def zdiff(
171
self,
172
keys: Iterable[KeyT],
173
withscores: bool = False
174
) -> Union[List[bytes], List[Tuple[bytes, float]]]: ...
175
176
def zdiffstore(self, dest: KeyT, keys: Iterable[KeyT]) -> ResponseT: ...
177
```
178
179
### Random Selection and Scanning
180
181
Functions for random member selection and iterating through large sorted sets.
182
183
```python { .api }
184
def zrandmember(
185
self,
186
key: KeyT,
187
count: Optional[int] = None,
188
withscores: bool = False
189
) -> Union[Optional[bytes], List[bytes], List[Tuple[bytes, float]]]: ...
190
191
def zscan(
192
self,
193
name: KeyT,
194
cursor: int = 0,
195
match: Optional[PatternT] = None,
196
count: Optional[int] = None,
197
score_cast_func: Optional[Callable] = float
198
) -> ResponseT: ...
199
200
def zscan_iter(
201
self,
202
name: KeyT,
203
match: Optional[PatternT] = None,
204
count: Optional[int] = None,
205
score_cast_func: Optional[Callable] = float
206
) -> Iterator[Tuple[bytes, float]]: ...
207
```
208
209
## Usage Examples
210
211
### Basic Sorted Set Operations
212
213
```python
214
import fakeredis
215
216
client = fakeredis.FakeRedis()
217
218
# Add members with scores
219
client.zadd('leaderboard', {
220
'alice': 100,
221
'bob': 85,
222
'charlie': 92,
223
'david': 78
224
})
225
226
# Add single member
227
client.zadd('leaderboard', {'eve': 95})
228
229
# Get sorted set size
230
size = client.zcard('leaderboard')
231
print(f"Leaderboard size: {size}") # 5
232
233
# Get member score
234
alice_score = client.zscore('leaderboard', 'alice')
235
print(f"Alice's score: {alice_score}") # 100.0
236
237
# Get multiple scores
238
scores = client.zmscore('leaderboard', ['alice', 'bob', 'unknown'])
239
print(f"Scores: {scores}") # [100.0, 85.0, None]
240
241
# Get member rank (0-based, lowest score = rank 0)
242
alice_rank = client.zrank('leaderboard', 'alice')
243
print(f"Alice's rank (asc): {alice_rank}") # 4 (highest score)
244
245
# Get reverse rank (highest score = rank 0)
246
alice_revrank = client.zrevrank('leaderboard', 'alice')
247
print(f"Alice's rank (desc): {alice_revrank}") # 0 (highest score)
248
```
249
250
### Range Queries
251
252
```python
253
import fakeredis
254
255
client = fakeredis.FakeRedis()
256
257
# Setup leaderboard
258
client.zadd('scores', {
259
'player1': 1500,
260
'player2': 1200,
261
'player3': 1800,
262
'player4': 900,
263
'player5': 1650
264
})
265
266
# Get top 3 players (highest scores)
267
top_players = client.zrevrange('scores', 0, 2, withscores=True)
268
print("Top 3 players:")
269
for player, score in top_players:
270
print(f" {player.decode()}: {score}")
271
272
# Get bottom 2 players (lowest scores)
273
bottom_players = client.zrange('scores', 0, 1, withscores=True)
274
print("Bottom 2 players:")
275
for player, score in bottom_players:
276
print(f" {player.decode()}: {score}")
277
278
# Get players with scores between 1000 and 1600
279
mid_range = client.zrangebyscore('scores', 1000, 1600, withscores=True)
280
print("Mid-range players (1000-1600):")
281
for player, score in mid_range:
282
print(f" {player.decode()}: {score}")
283
284
# Count players in score range
285
count = client.zcount('scores', 1200, 1700)
286
print(f"Players with scores 1200-1700: {count}")
287
```
288
289
### Leaderboard Implementation
290
291
```python
292
import fakeredis
293
294
class Leaderboard:
295
def __init__(self, client, name):
296
self.client = client
297
self.name = name
298
299
def add_score(self, player, score):
300
"""Add or update player score"""
301
return self.client.zadd(self.name, {player: score})
302
303
def increment_score(self, player, amount):
304
"""Increment player's score"""
305
return self.client.zincrby(self.name, amount, player)
306
307
def get_score(self, player):
308
"""Get player's current score"""
309
return self.client.zscore(self.name, player)
310
311
def get_rank(self, player):
312
"""Get player's rank (1-based, 1 = highest score)"""
313
rank = self.client.zrevrank(self.name, player)
314
return rank + 1 if rank is not None else None
315
316
def get_top_players(self, limit=10):
317
"""Get top N players"""
318
return self.client.zrevrange(self.name, 0, limit - 1, withscores=True)
319
320
def get_player_range(self, start_rank, end_rank):
321
"""Get players in rank range (1-based)"""
322
return self.client.zrevrange(
323
self.name,
324
start_rank - 1,
325
end_rank - 1,
326
withscores=True
327
)
328
329
def get_around_player(self, player, context=2):
330
"""Get players around a specific player"""
331
rank = self.client.zrevrank(self.name, player)
332
if rank is None:
333
return []
334
335
start = max(0, rank - context)
336
end = rank + context
337
return self.client.zrevrange(self.name, start, end, withscores=True)
338
339
def remove_player(self, player):
340
"""Remove player from leaderboard"""
341
return self.client.zrem(self.name, player)
342
343
# Usage
344
client = fakeredis.FakeRedis()
345
leaderboard = Leaderboard(client, 'game_scores')
346
347
# Add initial scores
348
players_scores = {
349
'Alice': 2500,
350
'Bob': 1800,
351
'Charlie': 2200,
352
'Diana': 1900,
353
'Eve': 2100
354
}
355
356
for player, score in players_scores.items():
357
leaderboard.add_score(player, score)
358
359
# Game events
360
leaderboard.increment_score('Bob', 300) # Bob scores 300 points
361
leaderboard.increment_score('Diana', 150) # Diana scores 150 points
362
363
# Query leaderboard
364
print("Top 3 players:")
365
for i, (player, score) in enumerate(leaderboard.get_top_players(3), 1):
366
print(f"{i}. {player.decode()}: {score}")
367
368
# Find Alice's position
369
alice_rank = leaderboard.get_rank('Alice')
370
alice_score = leaderboard.get_score('Alice')
371
print(f"Alice: Rank {alice_rank}, Score {alice_score}")
372
373
# Show players around Bob
374
around_bob = leaderboard.get_around_player('Bob')
375
print("Players around Bob:")
376
for player, score in around_bob:
377
print(f" {player.decode()}: {score}")
378
```
379
380
### Time-based Rankings
381
382
```python
383
import fakeredis
384
import time
385
386
class TimeBasedRanking:
387
def __init__(self, client, name):
388
self.client = client
389
self.name = name
390
391
def add_event(self, item, timestamp=None):
392
"""Add item with timestamp as score"""
393
if timestamp is None:
394
timestamp = time.time()
395
return self.client.zadd(self.name, {item: timestamp})
396
397
def get_recent_items(self, limit=10):
398
"""Get most recent items"""
399
return self.client.zrevrange(self.name, 0, limit - 1, withscores=True)
400
401
def get_items_since(self, since_timestamp):
402
"""Get items added since a specific timestamp"""
403
return self.client.zrangebyscore(
404
self.name,
405
since_timestamp,
406
'+inf',
407
withscores=True
408
)
409
410
def get_items_in_time_range(self, start_time, end_time):
411
"""Get items in a specific time range"""
412
return self.client.zrangebyscore(
413
self.name,
414
start_time,
415
end_time,
416
withscores=True
417
)
418
419
def cleanup_old_items(self, before_timestamp):
420
"""Remove items older than timestamp"""
421
return self.client.zremrangebyscore(self.name, '-inf', before_timestamp)
422
423
# Usage
424
client = fakeredis.FakeRedis()
425
activity = TimeBasedRanking(client, 'user_activity')
426
427
# Record activities with timestamps
428
current_time = time.time()
429
activity.add_event('login', current_time - 3600) # 1 hour ago
430
activity.add_event('page_view', current_time - 1800) # 30 min ago
431
activity.add_event('purchase', current_time - 600) # 10 min ago
432
activity.add_event('logout', current_time) # Now
433
434
# Get recent activities
435
recent = activity.get_recent_items(5)
436
print("Recent activities:")
437
for event, timestamp in recent:
438
print(f" {event.decode()}: {timestamp}")
439
440
# Get activities in last 45 minutes
441
since_45_min = activity.get_items_since(current_time - 2700)
442
print("Activities in last 45 minutes:")
443
for event, timestamp in since_45_min:
444
print(f" {event.decode()}: {timestamp}")
445
```
446
447
### Priority Queue Implementation
448
449
```python
450
import fakeredis
451
452
class PriorityQueue:
453
def __init__(self, client, name):
454
self.client = client
455
self.name = name
456
457
def enqueue(self, item, priority):
458
"""Add item with priority (higher number = higher priority)"""
459
return self.client.zadd(self.name, {item: priority})
460
461
def dequeue(self):
462
"""Remove and return highest priority item"""
463
result = self.client.zpopmax(self.name, 1)
464
if result:
465
return result[0][0].decode(), result[0][1] # item, priority
466
return None, None
467
468
def dequeue_multiple(self, count):
469
"""Remove and return multiple highest priority items"""
470
result = self.client.zpopmax(self.name, count)
471
return [(item.decode(), priority) for item, priority in result]
472
473
def peek(self):
474
"""Look at highest priority item without removing"""
475
result = self.client.zrevrange(self.name, 0, 0, withscores=True)
476
if result:
477
return result[0][0].decode(), result[0][1]
478
return None, None
479
480
def update_priority(self, item, new_priority):
481
"""Update item's priority"""
482
return self.client.zadd(self.name, {item: new_priority})
483
484
def size(self):
485
"""Get queue size"""
486
return self.client.zcard(self.name)
487
488
def is_empty(self):
489
"""Check if queue is empty"""
490
return self.size() == 0
491
492
# Usage
493
client = fakeredis.FakeRedis()
494
pq = PriorityQueue(client, 'task_queue')
495
496
# Add tasks with priorities
497
pq.enqueue('send_email', 1)
498
pq.enqueue('backup_database', 5)
499
pq.enqueue('process_payment', 10)
500
pq.enqueue('generate_report', 3)
501
502
# Process tasks by priority
503
while not pq.is_empty():
504
task, priority = pq.dequeue()
505
print(f"Processing: {task} (priority: {priority})")
506
```
507
508
### Score Manipulation and Set Operations
509
510
```python
511
import fakeredis
512
513
client = fakeredis.FakeRedis()
514
515
# Setup multiple score sets
516
client.zadd('math_scores', {'alice': 95, 'bob': 87, 'charlie': 92})
517
client.zadd('english_scores', {'alice': 88, 'bob': 91, 'diana': 89})
518
client.zadd('science_scores', {'alice': 93, 'charlie': 85, 'diana': 94})
519
520
# Increment scores
521
client.zincrby('math_scores', 5, 'bob') # Bob improves by 5 points
522
523
# Conditional adds (nx = only if doesn't exist, xx = only if exists)
524
client.zadd('math_scores', {'eve': 90}, nx=True) # Add new student
525
client.zadd('math_scores', {'alice': 100}, xx=True) # Update existing student
526
527
# Union of scores (sum by default)
528
client.zunionstore('total_scores', ['math_scores', 'english_scores', 'science_scores'])
529
530
# Union with weights
531
client.zunionstore(
532
'weighted_scores',
533
{'math_scores': 0.4, 'english_scores': 0.3, 'science_scores': 0.3}
534
)
535
536
# Intersection (students who have all three scores)
537
client.zinterstore('complete_scores', ['math_scores', 'english_scores', 'science_scores'])
538
539
# Get final rankings
540
total_rankings = client.zrevrange('weighted_scores', 0, -1, withscores=True)
541
print("Final weighted rankings:")
542
for student, score in total_rankings:
543
print(f" {student.decode()}: {score:.1f}")
544
```
545
546
### Random Selection from Sorted Set
547
548
```python
549
import fakeredis
550
551
client = fakeredis.FakeRedis()
552
553
# Setup a sorted set of items with popularity scores
554
client.zadd('popular_items', {
555
'item_a': 100,
556
'item_b': 85,
557
'item_c': 70,
558
'item_d': 95,
559
'item_e': 60
560
})
561
562
# Get random item
563
random_item = client.zrandmember('popular_items')
564
print(f"Random item: {random_item.decode()}")
565
566
# Get multiple random items with scores
567
random_items = client.zrandmember('popular_items', 3, withscores=True)
568
print("Random items with scores:")
569
for item, score in random_items:
570
print(f" {item.decode()}: {score}")
571
572
# Weighted random selection (simulate by score ranges)
573
def weighted_random_selection(client, key, count=1):
574
# Get total score sum
575
all_items = client.zrange(key, 0, -1, withscores=True)
576
total_score = sum(score for _, score in all_items)
577
578
# This is a simplified example - in practice you'd implement
579
# proper weighted random selection
580
return client.zrandmember(key, count, withscores=True)
581
582
weighted_selection = weighted_random_selection(client, 'popular_items', 2)
583
print("Weighted random selection:")
584
for item, score in weighted_selection:
585
print(f" {item.decode()}: {score}")
586
```
587
588
### Scanning Large Sorted Sets
589
590
```python
591
import fakeredis
592
593
client = fakeredis.FakeRedis()
594
595
# Create a large sorted set
596
for i in range(1000):
597
client.zadd('large_sorted_set', {f'member_{i}': i})
598
599
# Scan through all members
600
cursor = 0
601
all_members = []
602
603
while True:
604
cursor, members = client.zscan('large_sorted_set', cursor=cursor, count=100)
605
all_members.extend(members)
606
if cursor == 0:
607
break
608
609
print(f"Total members scanned: {len(all_members)}")
610
611
# Scan with pattern matching
612
matching_members = []
613
for member, score in client.zscan_iter('large_sorted_set', match='member_1*'):
614
matching_members.append((member.decode(), score))
615
616
print(f"Members matching 'member_1*': {len(matching_members)}")
617
618
# Use the iterator for efficient processing
619
high_scorers = []
620
for member, score in client.zscan_iter('large_sorted_set'):
621
if score > 900:
622
high_scorers.append((member.decode(), score))
623
624
print(f"High scorers (>900): {len(high_scorers)}")
625
```