ZRANGE、ZREVRANGE:获取指定索引范围内的成员

通过 ZRANGE 命令和 ZREVRANGE 命令,用户可以以升序排列或者降序排列方式,从有序集合中获取指定索引范围内的成员:

ZRANGE sorted_set start end
ZREVRANGE sorted_set start end

其中 ZRANGE 命令用于获取按照分值大小实施升序排列的成员,而 ZREVRANGE 命令则用于获取按照分值大小实施降序排列的成员。命令中的 start 索引和 end 索引指定的是闭区间索引范围,也就是说,位于这两个索引上的成员也会包含在命令返回的结果当中。

举个例子,如果我们想要获取 salary 有序集合在按照升序排列成员时,位于索引 0 至索引 3 范围内的成员,那么可以执行以下命令:

redis> ZRANGE salary 0 3
1) "peter"
2) "bob"
3) "jack"
4) "tom"

图6-15展示了这个 ZRANGE 命令的执行过程。

image 2025 01 03 19 37 13 355
Figure 1. 图6-15 ZRANGE命令执行示意图

如果我们想要获取 salary 有序集合在按照降序排列成员时,位于索引 2 至索引 4 范围内的成员,那么可以执行以下命令:

redis> ZREVRANGE salary 2 4
1) "jack"
2) "bob"
3) "peter"

图6-16展示了这个 ZREVRANGE 命令的执行过程。

image 2025 01 03 19 38 19 293
Figure 2. 图6-16 ZREVRANGE命令的执行示意图

使用负数索引

与第 4 章中介绍过的 LRANGE 命令类似,ZRANGE 命令和 ZREVRANGE 命令除了可以接受正数索引之外,还可以接受负数索引。

比如,如果我们想要以升序排列的方式获取 salary 有序集合的最后 3 个成员,那么可以执行以下命令:

redis> ZRANGE salary -3 -1
1) "jack"
2) "tom"
3) "mary"

图6-17展示了这个 ZRANGE 命令的执行过程。

image 2025 01 03 19 40 58 492
Figure 3. 图6-17 使用负数索引的ZRANGE命令的执行示意图

与此类似,如果我们想要以降序排列的方式获取 salary 有序集合的最后一个成员,那么可以执行以下命令:

redis> ZREVRANGE salary -1 -1
1) "peter"

图6-18 展示了这个 ZREVRANGE 命令的执行过程。

最后,如果我们想要以升序排列或者降序排列的方式获取 salary 有序集合包含的所有成员,那么只需要将起始索引设置为 0,结束索引设置为 -1,然后调用 ZRANGE 命令或者 ZREVRANGE 命令即可:

redis> ZRANGE salary 0 -1 -- 以升序排列方式获取所有成员
1) "peter"
2) "bob"
3) "jack"
4) "tom"
5) "mary"
redis> ZREVRANGE salary 0 -1 -- 以降序排列方式获取所有成员
1) "mary"
2) "tom"
3) "jack"
4) "bob"
5) "peter"
image 2025 01 03 19 42 34 659
Figure 4. 图6-18 使用负数索引的ZREVRANGE命令的执行示意图

获取成员及其分值

在默认情况下,ZRANGE 命令和 ZREVRANGE 命令只会返回指定索引范围内的成员,如果用户想要在获取这些成员的同时也获取与之相关联的分值,那么可以在调用 ZRANGE 命令或者 ZREVRANGE 命令的时候,给定可选的 WITHSCORES 选项:

ZRANGE sorted_set start end [WITHSCORES]
ZREVRANGE sorted_set start end [WITHSCORES]

以下代码展示了如何获取指定索引范围内的成员以及与这些成员相关联的分值:

redis> ZRANGE salary 0 3 WITHSCORES
1) "peter"
2) "3500" -- 成员"peter"的分值
3) "bob"
4) "3800" -- 成员"bob"的分值
5) "jack"
6) "4500" -- 成员"jack"的分值
7) "tom"
8) "5000" -- 成员"tom"的分值
redis> ZREVRANGE salary 2 4 WITHSCORES
1) "jack"
2) "4500" -- 成员"jack"的分值
3) "bob"
4) "3800" -- 成员"bob"的分值
5) "peter"
6) "3500" -- 成员"peter"的分值

处理不存在的有序集合

如果用户给定的有序集合并不存在,那么 ZRANGE 命令和 ZREVRANGE 命令将返回一个空列表:

redis> ZRANGE not-exists-sorted-set 0 10
(empty list or set)
redis> ZREVRANGE not-exists-sorted-set 0 10
(empty list or set)

其它信息

  • 复杂度:O(log(N)+M),其中 N 为有序集合包含的成员数量,而 M 则为命令返回的成员数量。

  • 版本要求:ZRANGE 命令和 ZREVRANGE 命令从 Redis 1.2.0 版本开始可用。

示例:排行榜

我们在网上常常会看到各式各样的排行榜,比如,在音乐网站上可能会看到试听排行榜、下载排行榜、华语歌曲排行榜和英语歌曲排行榜等,而在视频网站上可能会看到观看排行榜、购买排行榜、收藏排行榜等,甚至连项目托管网站 GitHub 都提供了各种不同的排行榜,以此来帮助用户找到近期最受人瞩目的新项目。

代码清单6-1 展示了一个使用有序集合实现的排行榜程序:

  • 这个程序使用 ZADD 命令向排行榜中添加被排序的元素及其分数,并使用 ZREVRANK 命令去获取元素在排行榜中的排名,以及使用 ZSCORE 命令去获取元素的分数。

  • 当用户不再需要对某个元素进行排序的时候,可以调用由 ZREM 命令实现的 remove() 方法,从排行榜中移除该元素。

  • 如果用户想要修改某个被排序元素的分数,那么只需要调用由 ZINCRBY 命令实现的 increase_score() 方法或者 decrease_score() 方法即可。

  • 当用户想要获取排行榜前 N 位的元素及其分数时,只需要调用由 ZREVRANGE 命令实现的 top() 方法即可。

代码清单6-1 使用有序集合实现的排行榜程序:/sorted_set/ranking_list.py

class RankingList:

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

    def set_score(self, item, score):
        """
        为排行榜中的指定元素设置分数,不存在的元素会被添加到排行榜里面。
        """
        self.client.zadd(self.key, {item:score})

    def get_score(self, item):
        """
        获取排行榜中指定元素的分数。
        """
        return self.client.zscore(self.key, item)

    def remove(self, item):
        """
        从排行榜中移除指定的元素。
        """
        self.client.zrem(self.key, item)

    def increase_score(self, item, increment):
        """
        将给定元素的分数增加 increment 分。
        """
        self.client.zincrby(self.key, increment, item)

    def decrease_score(self, item, decrement):
        """
        将给定元素的分数减少 decrement 分。
        """
        # 因为 Redis 没有直接提供能够减少元素分值的命令
        # 所以这里通过传入一个负数减量来达到减少分值的目的
        self.client.zincrby(self.key, 0-decrement, item)

    def get_rank(self, item):
        """
        获取给定元素在排行榜中的排名。
        """
        rank = self.client.zrevrank(self.key, item)
        # 因为 Redis 元素的排名是以 0 为开始的,
        # 而现实世界中的排名通常以 1 为开始,
        # 所以这里在返回排名之前会执行加一操作。
        if rank is not None:
            return rank+1

    def top(self, n, with_score=False):
        """
        获取排行榜中得分最高的 n 个元素,
        如果可选的 with_score 参数的值为 True ,那么将元素的分数(分值)也一并返回。
        """
        return self.client.zrevrange(self.key, 0, n-1, withscores=with_score)

举个例子,我们可以通过执行以下代码,创建出一个记录歌曲下载次数的排行榜:

>>> from redis import Redis
>>> from ranking_list import RankingList
>>> client = Redis(decode_responses=True)
>>> ranking = RankingList(client, "music download ranking")

接着通过以下代码记录歌曲的名字及其下载次数:

>>> ranking.set_score("ninelie", 3500)
>>> ranking.set_score("StarRingChild", 2700)
>>> ranking.set_score("RE:I AM", 3300)
>>> ranking.set_score("Your voice", 2200)
>>> ranking.set_score("theDOGS", 1800)

然后通过以下代码获取指定歌曲的下载次数,并获知它在排行榜中的位置:

>>> ranking.get_score("ninelie")
3500.0
>>> ranking.get_rank("ninelie")
1

最后还可以通过以下代码获取排行榜前5位的歌曲:

>>> ranking.top(5)
['ninelie', 'RE:I AM', 'StarRingChild', 'Your voice', 'theDOGS']
>>>
>>> ranking.top(5, True) # 在获取榜单的同时显示歌曲的下载次数
[('ninelie', 3500.0), ('RE:I AM', 3300.0), ('StarRingChild', 2700.0), ('Your voice', 2200.0), ('
theDOGS', 1800.0)]