ZCOUNT:统计指定分值范围内的成员数量

通过使用 COUNT 命令,用户可以统计出有序集合中分值介于指定范围之内的成员数量:

ZCOUNT sorted_set min max

比如,我们可以通过执行以下命令,统计出 salary 有序集合中分值介于 3000~5000 之间的成员数量:

redis> ZCOUNT salary 3000 5000
(integer) 4 -- 有序集合里面有4个成员的分值介于3000~5000之间

图6-21 展示了这个 ZCOUNT 命令统计出的 4 个成员。

image 2025 01 03 20 04 29 135
Figure 1. 图6-21 分值介于3000~5000之间的4个成员

分值范围的格式

ZCOUNT 命令接受的分值范围格式和 ZRANGEBYSCORE 命令接受的分值范围格式完全相同:用户可以在执行 ZCOUNT 命令时,使用 +inf 表示无穷大分值,使用 -inf 表示无穷小分值,或者使用单括号(定义开区间分值范围。

举个例子,如果我们想要统计 salary 有序集合中分值小于 5000 的成员有多少个,那么只需要执行以下代码即可:

redis> ZCOUNT salary -inf (5000
(integer) 3

其它信息

  • 复杂度:O(log(N)),其中 N 为有序集合包含的成员数量。

  • 版本要求:ZCOUNT 命令从 Redis 2.0.0 版本开始可用。

示例:时间线

在互联网上,有很多网站都会根据内容的发布时间来对内容进行排序,比如:

  • 博客系统会按照文章发布时间的先后,把最近发布的文章放在前面,而发布时间较早的文章则放在后面,这样访客在浏览博客的时候,就可以先阅读最新的文章,然后再阅读较早的文章。

  • 新闻网站会按照新闻的发布时间,把最近发生的新闻放在网站的前面,而早前发生的新闻则放在网站的后面,这样当用户访问该网站的时候,就可以第一时间查看到最新的新闻报道。

  • 诸如微博和 Twitter 这样的微博客都会把用户最新发布的消息放在页面的前面,而稍早之前发布的消息则放在页面的后面,这样用户就可以通过向后滚动网页,查看最近一段时间自己关注的人都发表了哪些动态。

类似的情形还有很多。通过对这类行为进行抽象,我们可以创建出代码清单6-2所示的时间线程序:

  • 这个程序会把被添加到时间线里面的元素用作成员,与元素相关联的时间戳用作分值,将元素和它的时间戳添加到有序集合中。

  • 因为时间线中的每个元素都有一个与之相关联的时间戳,所以时间线中的元素将按照时间戳的大小进行排序。

  • 通过对时间线中的元素执行 ZREVRANGE 命令或者 ZREVRANGEBYSCORE 命令,用户可以以分页的方式按顺序取出时间线中的元素,或者从时间线中取出指定时间区间内的元素。

代码清单6-2 使用有序集合实现的时间线程序:/sorted_set/timeline.py
class Timeline:

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

    def add(self, item, time):
        """
        将元素添加到时间线里面。
        """
        self.client.zadd(self.key, {item:time})

    def remove(self, item):
        """
        从时间线里面移除指定元素。
        """
        self.client.zrem(self.key, item)

    def count(self):
        """
        返回时间线包含的元素数量。
        """
        return self.client.zcard(self.key)

    def pagging(self, number, count, with_time=False):
        """
        按照每页 count 个元素计算,取出时间线第 number 页上的所有元素,
        这些元素将根据时间戳逆序排列。
        如果可选参数 with_time 的值为 True ,那么元素对应的时间戳也会一并被返回。
        注意:number 参数的起始值是 1 而不是 0 。
        """
        start_index = (number - 1)*count
        end_index = number*count-1
        return self.client.zrevrange(self.key, start_index, end_index, withscores=with_time)

    def fetch_by_time_range(self, min_time, max_time, number, count, with_time=False):
        """
        按照每页 count 个元素计算,获取指定时间段第 number 页上的所有元素,
        这些元素将根据时间戳逆序排列。
        如果可选参数 with_time 的值为 True ,那么元素对应的时间戳也会一并被返回。
        注意:number 参数的起始值是 1 而不是 0 。
        """
        start_index = (number-1)*count
        return self.client.zrevrangebyscore(self.key, max_time, min_time, start_index,
                                            count, withscores=with_time)

作为例子,让我们来学习一下如何使用这个时间线程序来存储和管理一系列博客文章。首先,我们需要载入相关的函数库,并创建一个时间线对象:

>>> from redis import Redis
>>> from timeline import Timeline
>>> client = Redis(decode_responses=True)
>>> blogs = Timeline(client, "blog_timelie")

通过 blogs 对象,我们可以把表 6-1 所示的 10 篇博客文章全部添加到时间线中:

>>> blogs.add("Switching from macOS: The Basics", 1477965600)
>>> blogs.add("Recent Loki Updates", 1477929600)
>>> blogs.add("What's Web Team Up To?", 1477645200)
>>> blogs.add("We've Joined the Snap Format TOB!", 1475618400)
>>> blogs.add("Loki Release Follow Up", 1474549200)
>>> blogs.add("Our Gtk+ Stylesheet Has Moved", 1473642000)
>>> blogs.add("Loki 0.4 Stable Release!", 1473404400)
>>> blogs.add("The Store is Back!", 1472068800)
>>> blogs.add("New Open Source Page On Our Website", 1470664800)
>>> blogs.add("We're back from the Snappy Sprint!", 1469664000)
image 2025 01 03 20 09 42 229
Figure 2. 表6-1 一些博客文章

在此之后,我们可以通过调用 count() 方法来获取时间线目前包含的文章数量:

>>> blogs.count()
10

也可以按照每页 5 篇文章的方式,按顺序获取位于时间线第 1 页的博客文章以及这些博客文章的发布时间:

>>> blogs.pagging(1, 5, with_time=True)
[('Switching from macOS: The Basics', 1477965600.0), ('Recent Loki Updates', 1477929600.0), ("Wh
at's Web Team Up To?", 1477645200.0), ("We've Joined the Snap Format TOB!", 1475618400.0), ('Lok
i Release Follow Up', 1474549200.0)]

或者以类似的方法,获取位于时间线第 2 页的博客文章:

>>> blogs.pagging(2, 5, with_time=True)
[('Our Gtk+ Stylesheet Has Moved', 1473642000.0), ('Loki 0.4 Stable Release!', 147340
4400.0), ('The Store is Back!', 1472068800.0), ('New Open Source Page On Our Website',
1470664800.0), ("We're back from the Snappy Sprint!", 1469664000.0)]

除了按照时间顺序获取博客文章之外,我们还可以通过指定时间区间的方式获取指定时间段内发布的博客文章。比如,以2016年9月1日0时0分0秒的时间戳 1472659200 为起点,2016年9月30日23时59分59秒的时间戳 1475251199 为终点,调用 fetch_by_time_range() 方法,就可以找出 9 月份发布的所有博客文章:

>>> blogs.fetch_by_time_range(1472659200, 1475251199, 1, 5, with_time=True)
[('Loki Release Follow Up', 1474549200.0), ('Our Gtk+ Stylesheet Has Moved', 14736
42000.0), ('Loki 0.4 Stable Release!', 1473404400.0)]

同样,以2016年11月1日0时0分0秒的时间戳 1477929600 为起点,2016年11月30日23时59分59秒的时间戳 1480521599 为终点,调用 fetch_by_time_range() 方法,就可以找出 11 月份发布的所有博客文章:

>>> blogs.fetch_by_time_range(1477929600, 1480521599, 1, 5, with_time=True)
[('Switching from macOS: The Basics', 1477965600.0), ('Recent Loki Updates', 1477
929600.0)]