redis技巧--自动完成功能实现

自动完成功能一般都伴随搜索框出现,就是用户在输入时帮助其自动补全。

比如对成语进行补全,现有如下成语:一心一意,一心二用,一帆风顺。

两种实现方式:

实现方式一:

为每个成语的每个前缀都使用一个集合类型键来存储该前缀对应的成语名,并且为了实现排序,我们使用有序集合,并score都为0,这样就按元素值的字典序排序。如果想要实现按照词的热度排序,需要再创建一个有序集合,存放词和score,最后把查询结果和这个集合做交集即可,这样可以避免更新一个词的热度需要更新多个集合的情况,因为一个词会出现在多个集合中。

做法:

对于上边的成语就会创建如下形式的有序集合(key : value,...),score都为0:

一 :一心一意,一心二用,一帆风顺

一心:一心一意,一心二用

一心一:一心一意

一心二:一心二用

一帆:一帆风顺

一帆风:一帆风顺

下边实现生成这些前缀对应的有序集合的代码

$valueArr = ['一心一意','一心二用','一帆风顺'];
//中文字符注意指定编码,不能用$str[$i]方式获取中文
foreach ($valueArr as $item) {
    for ($i = 1,$len = mb_strlen($item, "utf-8"); $i < $len; $i++) {
        $k = $prefix.mb_substr($item,0, $i, "utf-8");
        $redis->zadd($k, 0, $item);
    }
}

在通过对应的关键字获取对应的有序集合即可

$search1 = "一";
$search2 = "一帆";
$ret1 = $redis->zRange($$prefix.$search1, 0, -1);
$ret2 = $redis->zRange($$prefix.$search2, 0, -1);

如果需要实现热度排序,只需要取关键字集合和全部成语的热度集合的交集即可,通过参数指定两个集合的分数组合方式

$redis->zInter($retZset, [$prefix.$search, $hotZset]); //取交集
$ret = $redis->zRevRange($retZset, 0, -1); //倒序

 

实现方式二:

通过有序集合实现,该方法由redis作者介绍。因为有序集合当元素的score相同时,按照元素的字典序排序,利用这个特性只用一个有序集合就能实现标签补全。

做法:

把全部成语前缀存入有序集合,再把全部成语后边加上*号后存入有序集合,分数均为0。

获取时先取关键字在zset中的排名,然后在获取排名后的N个元素,最后遍历结果,找出*结尾并且以对应关键字开头的元素即是结果。

先实现存入代码

//遍历全部的前缀和成语加入有序集合
foreach ($valueArr as $item) {
    for ($i = 1,$len = mb_strlen($item, "utf-8"); $i <= $len; $i++) {
        $member = mb_substr($item,0, $i, "utf-8");
        if ($i == $len) {
            $member .= "*"; //非前缀需要在后边补*
        }
        $redis->zadd("auto:zset", 0, $member);
    }
}

再实现获取代码

//查询
$search = "一帆";
$rank = $redis->zRank("auto:zset", $search);
$ret = $redis->zRange("auto:zset", $rank + 1, $rank + 10); //获取10个
$ret = array_filter($ret, function($d){
    return ("*" === mb_substr($d, -1)); //此处没有去掉*,没有匹配前缀,多出来不匹配条数可以作为推荐
});
echo "<pre>";print_r($ret);exit;

 

总结:

方式一需要N个集合存储,但每个集合数据量较小,方式二只用一个集合实现,但数据量大,同时实现热度排序比较困难,这个看具体使用场景和数据量选择吧。

 

posted @ 2016-04-01 19:06  leezhxing  阅读(1486)  评论(0编辑  收藏  举报