docs
0
# Set Operations
1
2
Redis set data type operations providing unordered collections of unique strings. Sets are ideal for tracking unique items, performing set algebra operations (union, intersection, difference), and implementing features like tags, categories, and relationship tracking. Sets can hold up to 2^32 - 1 members.
3
4
## Capabilities
5
6
### Basic Set Operations
7
8
Core set manipulation functions for adding, removing, and testing membership.
9
10
```python { .api }
11
def sadd(self, name: KeyT, *values: EncodableT) -> ResponseT: ...
12
13
def srem(self, name: KeyT, *values: EncodableT) -> ResponseT: ...
14
15
def sismember(self, name: KeyT, value: EncodableT) -> ResponseT: ...
16
17
def smismember(self, name: KeyT, values: Iterable[EncodableT]) -> List[bool]: ...
18
19
def scard(self, name: KeyT) -> ResponseT: ...
20
21
def smembers(self, name: KeyT) -> Set[bytes]: ...
22
23
def spop(self, name: KeyT, count: Optional[int] = None) -> Union[Optional[bytes], List[bytes]]: ...
24
25
def srandmember(self, name: KeyT, number: Optional[int] = None) -> Union[Optional[bytes], List[bytes]]: ...
26
```
27
28
### Set Algebra Operations
29
30
Functions for performing mathematical set operations between multiple sets.
31
32
```python { .api }
33
def sinter(self, keys: Union[KeyT, Iterable[KeyT]], *args: KeyT) -> Set[bytes]: ...
34
35
def sintercard(self, numkeys: int, keys: Iterable[KeyT], limit: int = 0) -> ResponseT: ...
36
37
def sinterstore(self, dest: KeyT, keys: Union[KeyT, Iterable[KeyT]], *args: KeyT) -> ResponseT: ...
38
39
def sunion(self, keys: Union[KeyT, Iterable[KeyT]], *args: KeyT) -> Set[bytes]: ...
40
41
def sunionstore(self, dest: KeyT, keys: Union[KeyT, Iterable[KeyT]], *args: KeyT) -> ResponseT: ...
42
43
def sdiff(self, keys: Union[KeyT, Iterable[KeyT]], *args: KeyT) -> Set[bytes]: ...
44
45
def sdiffstore(self, dest: KeyT, keys: Union[KeyT, Iterable[KeyT]], *args: KeyT) -> ResponseT: ...
46
```
47
48
### Set Movement Operations
49
50
Functions for moving members between sets.
51
52
```python { .api }
53
def smove(self, src: KeyT, dst: KeyT, value: EncodableT) -> ResponseT: ...
54
```
55
56
### Set Scanning
57
58
Iterator functions for efficiently traversing large sets.
59
60
```python { .api }
61
def sscan(
62
self,
63
name: KeyT,
64
cursor: int = 0,
65
match: Optional[PatternT] = None,
66
count: Optional[int] = None
67
) -> ResponseT: ...
68
69
def sscan_iter(
70
self,
71
name: KeyT,
72
match: Optional[PatternT] = None,
73
count: Optional[int] = None
74
) -> Iterator[bytes]: ...
75
```
76
77
## Usage Examples
78
79
### Basic Set Operations
80
81
```python
82
import fakeredis
83
84
client = fakeredis.FakeRedis()
85
86
# Add members to sets
87
client.sadd('fruits', 'apple', 'banana', 'orange')
88
client.sadd('colors', 'red', 'yellow', 'orange')
89
90
# Check membership
91
is_member = client.sismember('fruits', 'apple')
92
print(f"Apple in fruits: {is_member}") # True
93
94
# Check multiple memberships
95
memberships = client.smismember('fruits', ['apple', 'grape', 'banana'])
96
print(f"Memberships: {memberships}") # [True, False, True]
97
98
# Get set size
99
fruit_count = client.scard('fruits')
100
print(f"Number of fruits: {fruit_count}") # 3
101
102
# Get all members
103
all_fruits = client.smembers('fruits')
104
print("All fruits:", {member.decode() for member in all_fruits})
105
106
# Remove members
107
removed_count = client.srem('fruits', 'banana', 'grape')
108
print(f"Removed {removed_count} items") # 1 (grape doesn't exist)
109
```
110
111
### Random Selection and Sampling
112
113
```python
114
import fakeredis
115
116
client = fakeredis.FakeRedis()
117
118
# Setup a set of items
119
client.sadd('items', 'item1', 'item2', 'item3', 'item4', 'item5', 'item6')
120
121
# Get random member without removing
122
random_item = client.srandmember('items')
123
print(f"Random item: {random_item.decode()}")
124
125
# Get multiple random members (with possible duplicates)
126
random_items = client.srandmember('items', 3)
127
print("Random items:", [item.decode() for item in random_items])
128
129
# Get multiple random members (without duplicates)
130
unique_random = client.srandmember('items', -3) # Negative count = no duplicates
131
print("Unique random items:", [item.decode() for item in unique_random])
132
133
# Pop random members (removes them from set)
134
popped_item = client.spop('items')
135
print(f"Popped item: {popped_item.decode()}")
136
137
# Pop multiple items
138
popped_items = client.spop('items', 2)
139
print("Popped items:", [item.decode() for item in popped_items])
140
141
# Check remaining items
142
remaining = client.smembers('items')
143
print("Remaining items:", {item.decode() for item in remaining})
144
```
145
146
### Set Algebra Operations
147
148
```python
149
import fakeredis
150
151
client = fakeredis.FakeRedis()
152
153
# Setup test sets
154
client.sadd('programming_languages', 'python', 'java', 'javascript', 'go')
155
client.sadd('web_languages', 'javascript', 'php', 'python', 'ruby')
156
client.sadd('compiled_languages', 'java', 'go', 'c', 'rust')
157
158
# Intersection - languages that are both programming and web languages
159
web_and_prog = client.sinter('programming_languages', 'web_languages')
160
print("Web & Programming:", {lang.decode() for lang in web_and_prog})
161
162
# Union - all languages combined
163
all_languages = client.sunion('programming_languages', 'web_languages', 'compiled_languages')
164
print("All languages:", {lang.decode() for lang in all_languages})
165
166
# Difference - programming languages that are not web languages
167
prog_not_web = client.sdiff('programming_languages', 'web_languages')
168
print("Programming but not Web:", {lang.decode() for lang in prog_not_web})
169
170
# Store intersection result
171
client.sinterstore('web_programming', 'programming_languages', 'web_languages')
172
stored_result = client.smembers('web_programming')
173
print("Stored intersection:", {lang.decode() for lang in stored_result})
174
175
# Intersection cardinality (count without returning members)
176
intersection_count = client.sintercard(2, ['programming_languages', 'compiled_languages'])
177
print(f"Programming & Compiled count: {intersection_count}")
178
```
179
180
### Moving Members Between Sets
181
182
```python
183
import fakeredis
184
185
client = fakeredis.FakeRedis()
186
187
# Setup source and destination sets
188
client.sadd('todo', 'task1', 'task2', 'task3')
189
client.sadd('completed', 'old_task')
190
191
# Move task from todo to completed
192
moved = client.smove('todo', 'completed', 'task1')
193
print(f"Moved task1: {moved}") # 1 if successful
194
195
# Try to move non-existent member
196
moved = client.smove('todo', 'completed', 'nonexistent')
197
print(f"Moved nonexistent: {moved}") # 0 if member doesn't exist
198
199
# Check final state
200
todo_items = client.smembers('todo')
201
completed_items = client.smembers('completed')
202
print("TODO:", {item.decode() for item in todo_items})
203
print("Completed:", {item.decode() for item in completed_items})
204
```
205
206
### Scanning Large Sets
207
208
```python
209
import fakeredis
210
211
client = fakeredis.FakeRedis()
212
213
# Create a large set
214
for i in range(1000):
215
client.sadd('large_set', f'member_{i}')
216
217
# Scan through all members
218
cursor = 0
219
all_members = []
220
221
while True:
222
cursor, members = client.sscan('large_set', cursor=cursor, count=100)
223
all_members.extend([member.decode() for member in members])
224
if cursor == 0:
225
break
226
227
print(f"Total members scanned: {len(all_members)}")
228
229
# Scan with pattern matching
230
matching_members = []
231
for member in client.sscan_iter('large_set', match='member_1*'):
232
matching_members.append(member.decode())
233
234
print(f"Members matching 'member_1*': {len(matching_members)}")
235
```
236
237
### Pattern: Tag System
238
239
```python
240
import fakeredis
241
242
class TagSystem:
243
def __init__(self, client):
244
self.client = client
245
246
def add_tags(self, item_id, *tags):
247
"""Add tags to an item"""
248
return self.client.sadd(f'item:{item_id}:tags', *tags)
249
250
def remove_tags(self, item_id, *tags):
251
"""Remove tags from an item"""
252
return self.client.srem(f'item:{item_id}:tags', *tags)
253
254
def get_tags(self, item_id):
255
"""Get all tags for an item"""
256
tags = self.client.smembers(f'item:{item_id}:tags')
257
return {tag.decode() for tag in tags}
258
259
def has_tag(self, item_id, tag):
260
"""Check if item has a specific tag"""
261
return bool(self.client.sismember(f'item:{item_id}:tags', tag))
262
263
def get_items_with_all_tags(self, *tags):
264
"""Get items that have all specified tags"""
265
# This is a simplified version - in practice, you'd maintain reverse indexes
266
# Here we demonstrate the set operations concept
267
tag_keys = [f'tag:{tag}:items' for tag in tags]
268
if tag_keys:
269
item_ids = self.client.sinter(tag_keys)
270
return {item_id.decode() for item_id in item_ids}
271
return set()
272
273
def add_item_to_tag_index(self, item_id, tag):
274
"""Add item to tag's reverse index"""
275
return self.client.sadd(f'tag:{tag}:items', item_id)
276
277
def remove_item_from_tag_index(self, item_id, tag):
278
"""Remove item from tag's reverse index"""
279
return self.client.srem(f'tag:{tag}:items', item_id)
280
281
# Usage
282
client = fakeredis.FakeRedis()
283
tag_system = TagSystem(client)
284
285
# Add tags to items
286
tag_system.add_tags('article:1', 'python', 'programming', 'tutorial')
287
tag_system.add_tags('article:2', 'python', 'web', 'flask')
288
tag_system.add_tags('article:3', 'javascript', 'programming', 'tutorial')
289
290
# Build reverse indexes
291
for item, tags in [
292
('article:1', ['python', 'programming', 'tutorial']),
293
('article:2', ['python', 'web', 'flask']),
294
('article:3', ['javascript', 'programming', 'tutorial'])
295
]:
296
for tag in tags:
297
tag_system.add_item_to_tag_index(item, tag)
298
299
# Query operations
300
python_articles = client.smembers('tag:python:items')
301
print("Python articles:", {item.decode() for item in python_articles})
302
303
# Find articles that are both programming and tutorials
304
prog_tutorials = client.sinter('tag:programming:items', 'tag:tutorial:items')
305
print("Programming tutorials:", {item.decode() for item in prog_tutorials})
306
307
# Check article tags
308
article_tags = tag_system.get_tags('article:1')
309
print("Article 1 tags:", article_tags)
310
```
311
312
### Pattern: User Permissions System
313
314
```python
315
import fakeredis
316
317
class PermissionSystem:
318
def __init__(self, client):
319
self.client = client
320
321
def grant_permission(self, user_id, *permissions):
322
"""Grant permissions to a user"""
323
return self.client.sadd(f'user:{user_id}:permissions', *permissions)
324
325
def revoke_permission(self, user_id, *permissions):
326
"""Revoke permissions from a user"""
327
return self.client.srem(f'user:{user_id}:permissions', *permissions)
328
329
def has_permission(self, user_id, permission):
330
"""Check if user has a specific permission"""
331
return bool(self.client.sismember(f'user:{user_id}:permissions', permission))
332
333
def has_any_permission(self, user_id, *permissions):
334
"""Check if user has any of the specified permissions"""
335
user_perms = self.client.smembers(f'user:{user_id}:permissions')
336
user_perms_set = {perm.decode() for perm in user_perms}
337
return any(perm in user_perms_set for perm in permissions)
338
339
def has_all_permissions(self, user_id, *permissions):
340
"""Check if user has all specified permissions"""
341
memberships = self.client.smismember(f'user:{user_id}:permissions', permissions)
342
return all(memberships)
343
344
def get_user_permissions(self, user_id):
345
"""Get all permissions for a user"""
346
perms = self.client.smembers(f'user:{user_id}:permissions')
347
return {perm.decode() for perm in perms}
348
349
def get_common_permissions(self, *user_ids):
350
"""Get permissions common to all specified users"""
351
if not user_ids:
352
return set()
353
354
keys = [f'user:{user_id}:permissions' for user_id in user_ids]
355
common = self.client.sinter(keys)
356
return {perm.decode() for perm in common}
357
358
# Usage
359
client = fakeredis.FakeRedis()
360
perms = PermissionSystem(client)
361
362
# Grant permissions
363
perms.grant_permission('user:alice', 'read_posts', 'write_posts', 'edit_profile')
364
perms.grant_permission('user:bob', 'read_posts', 'edit_profile', 'admin_panel')
365
perms.grant_permission('user:charlie', 'read_posts', 'moderate_comments')
366
367
# Check permissions
368
can_write = perms.has_permission('user:alice', 'write_posts')
369
print(f"Alice can write posts: {can_write}")
370
371
can_admin = perms.has_any_permission('user:bob', 'admin_panel', 'super_admin')
372
print(f"Bob has admin permissions: {can_admin}")
373
374
has_basic_perms = perms.has_all_permissions('user:charlie', 'read_posts', 'write_posts')
375
print(f"Charlie has basic permissions: {has_basic_perms}")
376
377
# Find common permissions
378
common = perms.get_common_permissions('user:alice', 'user:bob', 'user:charlie')
379
print(f"Common permissions: {common}")
380
```
381
382
### Pattern: Friend/Follower System
383
384
```python
385
import fakeredis
386
387
class SocialSystem:
388
def __init__(self, client):
389
self.client = client
390
391
def follow(self, follower_id, following_id):
392
"""User follows another user"""
393
# Add to follower's following set
394
self.client.sadd(f'user:{follower_id}:following', following_id)
395
# Add to followed user's followers set
396
self.client.sadd(f'user:{following_id}:followers', follower_id)
397
398
def unfollow(self, follower_id, following_id):
399
"""User unfollows another user"""
400
self.client.srem(f'user:{follower_id}:following', following_id)
401
self.client.srem(f'user:{following_id}:followers', follower_id)
402
403
def get_followers(self, user_id):
404
"""Get all followers of a user"""
405
followers = self.client.smembers(f'user:{user_id}:followers')
406
return {follower.decode() for follower in followers}
407
408
def get_following(self, user_id):
409
"""Get all users that this user is following"""
410
following = self.client.smembers(f'user:{user_id}:following')
411
return {user.decode() for user in following}
412
413
def is_following(self, follower_id, following_id):
414
"""Check if one user is following another"""
415
return bool(self.client.sismember(f'user:{follower_id}:following', following_id))
416
417
def get_mutual_follows(self, user1_id, user2_id):
418
"""Get users that both users are following"""
419
mutual = self.client.sinter(
420
f'user:{user1_id}:following',
421
f'user:{user2_id}:following'
422
)
423
return {user.decode() for user in mutual}
424
425
def suggest_follows(self, user_id):
426
"""Suggest users to follow based on mutual connections"""
427
following = self.get_following(user_id)
428
suggestions = set()
429
430
# Find followers of people this user follows
431
for followed_user in following:
432
their_following = self.get_following(followed_user)
433
suggestions.update(their_following)
434
435
# Remove already following and self
436
suggestions.discard(user_id)
437
suggestions -= following
438
439
return suggestions
440
441
# Usage
442
client = fakeredis.FakeRedis()
443
social = SocialSystem(client)
444
445
# Build social network
446
social.follow('alice', 'bob')
447
social.follow('alice', 'charlie')
448
social.follow('bob', 'charlie')
449
social.follow('bob', 'david')
450
social.follow('charlie', 'david')
451
social.follow('david', 'alice')
452
453
# Query the network
454
alice_following = social.get_following('alice')
455
print(f"Alice is following: {alice_following}")
456
457
bob_followers = social.get_followers('bob')
458
print(f"Bob's followers: {bob_followers}")
459
460
mutual = social.get_mutual_follows('alice', 'bob')
461
print(f"Alice and Bob both follow: {mutual}")
462
463
# Get suggestions for Alice
464
suggestions = social.suggest_follows('alice')
465
print(f"Suggested follows for Alice: {suggestions}")
466
```