示例:唯一计数器

本书前面在对字符串键以及散列键进行介绍的时候,曾经展示过如何使用这两种键去实现计数器程序。我们当时实现的计数器的作用都非常单纯:每当某个动作被执行时,程序就可以调用计数器的加法操作或者减法操作,对动作的执行次数进行记录。

以上这种简单的计数行为在大部分时候都是有用的,但是在某些情况下,我们需要一种要求更为严格的计数器,这种计数器只会对特定的动作或者对象进行一次计数而不是多次计数。

举个例子,一个网站的受欢迎程度通常可以用浏览量和用户数量这两个指标进行描述:

  • 浏览量记录的是网站页面被用户访问的总次数,网站的每个用户都可以重复地对同一个页面进行多次访问,而这些访问会被浏览量计数器一个不漏地被记录起来。

  • 至于用户数量记录的则是访问网站的 IP 地址数量,即使同一个 IP 地址多次访问相同的页面,用户数量计数器也只会对这个 IP 地址进行一次计数。

对于网站的浏览量,我们可以继续使用字符串键或者散列键实现的计数器进行计数;但如果我们想要记录网站的用户数量,那么就需要构建一个新的计数器,这个计数器对于每个特定的 IP 地址只会进行一次计数,我们把这种对每个对象只进行一次计数的计数器称之为唯一计数器(unique counter)。

代码清单 5-1 展示了一个使用集合实现的唯一计数器,这个计数器通过把被计数的对象添加到集合来保证每个对象只会被计数一次,然后通过获取集合的大小来判断计数器目前总共对多少个对象进行了计数。


代码清单 5-1 使用集合实现唯一计数器:/set/unique_counter.py

  1. class UniqueCounter:
  2.  
  3. def __init__(self, client, key):
  4. self.client = client
  5. self.key = key
  6.  
  7. def count_in(self, item):
  8. """
  9. 尝试将给定元素计入到计数器当中:
  10. 如果给定元素之前没有被计数过,那么方法返回 True 表示此次计数有效;
  11. 如果给定元素之前已经被计数过,那么方法返回 False 表示此次计数无效。
  12. """
  13. return self.client.sadd(self.key, item) == 1
  14.  
  15. def get_result(self):
  16. """
  17. 返回计数器的值。
  18. """
  19. return self.client.scard(self.key)

以下代码展示了如何使用唯一计数器去计算网站的用户数量:

  1. >>> from redis import Redis
  2. >>> from unique_counter import UniqueCounter
  3. >>> client = Redis(decode_responses=True)
  4. >>> counter = UniqueCounter(client, 'ip counter')
  5. >>> counter.count_in('8.8.8.8') # 将一些 IP 地址添加到计数器当中
  6. True
  7. >>> counter.count_in('9.9.9.9')
  8. True
  9. >>> counter.count_in('10.10.10.10')
  10. True
  11. >>> counter.get_result() # 获取计数结果
  12. 3
  13. >>> counter.count_in('8.8.8.8') # 添加一个已存在的 IP 地址
  14. False
  15. >>> counter.get_result() # 计数结果没有发生变化
  16. 3