使用 Redis 实现自动补全

Note

本文摘录自即将出版的《Redis使用手册》, 详情请见: RedisGuide.com

包含大量信息的网站常常会在搜索或者查找功能上提供自动补全特性, 这一特性可以帮助用户更快速地找到他们想要的信息。 比如说, 当我们在搜索引擎上输入 "黄" 字的时候, 搜索引擎的自动补全特性就会向我们列出一些比较著名的以 "黄" 字开头的人或者物, 从而使得我们可以更快速地找到与这些人或物相关的信息, 如图 6-39 所示。


图 6-39 搜索引擎通过自动补全向用户提示他可能感兴趣的结果

../_images/ac-example-1.png

代码清单 6-4 展示了一个使用有序集合实现的自动补全程序, 这个程序可以提供类似图 6-39 所示的自动补全效果。


代码清单 6-4 使用有序集合实现的自动补全程序:/sorted_set/auto_complete.py

class AutoComplete:

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

    def feed(self, content, weight=1):
        """
        根据用户输入的内容构建自动补全结果,
        其中 content 参数为内容本身,而可选的 weight 参数则用于指定内容的权重值。
        """
        for i in range(1, len(content)):
            key = "auto_complete::" + content[:i]
            self.client.zincrby(key, weight, content)

    def hint(self, prefix, count):
        """
        根据给定的前缀 prefix ,获取 count 个自动补全结果。
        """
        key = "auto_complete::" + prefix
        return self.client.zrevrange(key, 0, count-1)

这个自动补全程序的 feed() 方法接受给定的内容和权重值作为参数, 并以此来构建自动补全结果。 比如说, 如果我们调用 feed("黄晓明", 5000) , 那么程序将拼接出以下三个键:

auto_complete::
auto_complete::黄晓
auto_complete::黄晓明

然后通过执行以下这三个命令, 将自动补全结果 "黄晓明" 及其权重 5000 添加到相应的有序集合里面:

ZINCRBY  auto_complete::  5000  "黄晓明"
ZINCRBY  auto_complete::黄晓  5000  "黄晓明"
ZINCRBY  auto_complete::黄晓明  5000  "黄晓明"

这样做的结果是, 程序会把所有 "黄" 字开头的名字按权重大小有序地储存到 auto_complete::黄 这个有序集合里面, 而以 "黄晓" 开头的名字则会按照权重大小有序地储存在 auto_complete::黄晓 这个有序集合里面, 诸如此类。

相对的, 当我们想要找出所有以 "黄" 字开头的名字时, 只需要调用 hint() 方法, 程序就会使用 ZREVRANGE 命令, 从 auto_complete::黄 有序集合中取出相应的自动补全结果。

作为例子, 现在让我们载入这个自动补全程序:

>>> from redis import Redis
>>> from auto_complete import AutoComplete
>>> client = Redis(decode_responses=True)
>>> ac = AutoComplete(client)

然后向程序输入一些名字, 以及这些名字的权重:

>>> ac.feed("黄健宏", 30)
>>> ac.feed("黄健翔", 3000)
>>> ac.feed("黄晓明", 5000)
>>> ac.feed("张三", 2500)
>>> ac.feed("李四", 1700)

在此之后, 如果我们以 "黄" 字为前缀调用 hint() 方法, 程序就会列出所有以 "黄" 字开头的名字:

>>> for name in ac.hint("黄", 10):
...   print(name)
...
黄晓明
黄健翔
黄健宏

接着, 如果我们以 "黄健" 二字为前缀调用 hint() 方法, 那么程序将列出两个以 "黄健" 二字为开头的名字:

>>> for name in ac.hint("黄健", 10):
...   print(name)
...
黄健翔
黄健宏

再次提醒一下, 因为 hint() 方法是按照权重的大小有序地返回结果的, 所以权重较高的 "黄健翔" 会排在前面, 而权重较低的 "黄健宏" 则会排在后面。