SPOP:随机地从集合中移除指定数量的元素

通过使用 SPOP 命令,用户可以从集合中随机地移除指定数量的元素。SPOP 命令接受一个可选的 count 参数,用于指定需要被移除的元素数量。如果用户没有给定这个参数,那么 SPOP 命令默认只移除一个元素:

SPOP key [count]

SPOP 命令会返回被移除的元素作为命令的返回值。

举个例子,对于包含以下元素的 databases 集合来说:

redis> SMEMBERS databases
1) "MS SQL"
2) "MongoDB"
3) "Redis"
4) "Neo4j"
5) "PostgreSQL"
6) "MySQL"
7) "Oracle"
8) "CouchDB"

我们可以使用 SPOP 命令随机地移除 databases 集合中的元素:

redis> SPOP databases -- 随机地移除1个元素
"CouchDB" -- 被移除的是"CouchDB"元素
redis> SPOP databases -- 随机地移除1个元素
"Redis" -- 被移除的是"Redis"元素
redis> SPOP databases 3 -- 随机地移除3个元素
1) "Neo4j" -- 被移除的元素是"Neo4j"、"PostgreSQL"和"MySQL"
2) "PostgreSQL"
3) "MySQL"

图5-11展示了 databases 集合在执行各个 SPOP 命令时的变化过程。

image 2025 01 03 18 26 05 662
Figure 1. 图5-11 databases集合在执行SPOP命令时的变化过程

SPOP 与 SRANDMEMBER 的区别

SPOP 命令和 SRANDMEMBER 命令的主要区别在于,SPOP 命令会移除被随机选中的元素,而 SRANDMEMBER 命令则不会移除被随机选中的元素。

通过查看 databases 集合目前包含的元素,我们可以证实之前被 SPOP 命令选中的元素已经不在集合当中了:

redis> SMEMBERS databases
1) "MS SQL"
2) "MongoDB"
3) "Oracle"

SPOP 命令与 SRANDMEMBER 命令的另一个不同点在于,SPOP 命令只接受正数 count 值,如果向 SPOP 命令提供负数 count 值将引发错误,因为负数 count 值对于 SPOP 命令是没有意义的:

redis> SPOP databases -3
(error) ERR index out of range

其它信息

  • 复杂度:O(N),其中 N 为被移除的元素数量。

  • 版本要求:不带 count 参数的 SPOP 命令从 Redis 1.0.0 版本开始可用; 带有 count 参数的 SPOP 命令从 Redis 3.2.0 版本开始可用。

示例:抽奖

为了推销商品并回馈消费者,商家经常会举办一些抽奖活动,每个符合条件的消费者都可以参加这种抽奖,而商家则需要从所有参加抽奖的消费者中选出指定数量的获奖者,并向他们赠送物品、金钱或者其他购物优惠。

代码清单5-6展示了一个使用集合实现的抽奖程序,这个程序会把所有参与抽奖活动的玩家都添加到一个集合中,然后通过 SRANDMEMBER 命令随机地选出获奖者。

代码清单5-6 使用集合实现的抽奖程序:/set/lottery.py
class Lottery:

    def __init__(self, client, key):
        self.client = client
        self.key = key

    def add_player(self, user):
        """
        将用户添加到抽奖名单当中。
        """
        self.client.sadd(self.key, user)

    def get_all_players(self):
        """
        返回参加抽奖活动的所有用户。
        """
        return self.client.smembers(self.key)

    def player_count(self):
        """
        返回参加抽奖活动的用户人数。
        """
        return self.client.scard(self.key)

    def draw(self, number):
        """
        抽取指定数量的获奖者。
        """
        return self.client.srandmember(self.key, number)

考虑到保留完整的抽奖者名单可能会有用,所以这个抽奖程序使用了随机获取元素的 SRANDMEMBER 命令而不是随机移除元素的 SPOP 命令。在不需要保留完整的抽奖者名单的情况下,我们也可以使用 SPOP 命令去实现抽奖程序。

以下代码简单地展示了这个抽奖程序的使用方法:

>>> from redis import Redis
>>> from lottery import Lottery
>>> client = Redis(decode_responses=True)
>>> lottery = Lottery(client, 'birthday party lottery') # 这是一次生日派对抽奖活动
>>> lottery.add_player('peter') # 添加抽奖者
>>> lottery.add_player('jack')
>>> lottery.add_player('tom')
>>> lottery.add_player('mary')
>>> lottery.add_player('dan')
>>> lottery.player_count() # 查看抽奖者数量
5
>>> lottery.draw(1) # 抽取一名获奖者
['dan'] # dan中奖了