redis - 自动补全
- 前缀提示
- 多个单词提示
前缀提示
原理: 利用 zset 相同score 按照自然排序
- 添加相同score 的 value
- 用 zrank 查找第一个匹配的 index
- 用 zrange 遍历
zadd search 0 n
zadd search 0 nc
# 查找第一个匹配的前缀的index
zrank search n => index
# 从 index 后开始遍历
zrange search index, -1
service
@Service
public class AutoCompleteService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String AC_KEY = "search";
/**
* zadd search 0 n
* zadd search 0 nc
* zadd search 0 nab
*
* //search
* zrank search n => index
* zrange search index, -1
*
* input:
* n => n,nab
* na => nil
* nab => nab
*
* @param prefix
* @return
*/
public List<String> autoComplete(String prefix) {
ZSetOperations zSet = redisTemplate.opsForZSet();
Long rank = zSet.rank(AC_KEY, prefix);
if (rank == null) {
return null;
}
int limit = 5;
Set<String> range = zSet.range(AC_KEY, rank, rank + limit);
return range.stream().filter(a -> a.startsWith(prefix)).collect(Collectors.toList());
}
}
controller
@GetMapping("/auto/cmp")
public List<String> autoComplete(@RequestParam String prefix) {
return autoCompleteService.autoComplete(prefix);
}
多个单词提示
原理: 为字符串中的每个字符都创建 一个这个前缀字符的zset ,zset 中的value都保存了字符
如: 黄健强
就要创建三个 zset
* 7) "ap:黄"
* 8) "ap:黄健"
* 6) "ap:黄健强"
- 分前缀保存
- 前缀查找
service
@Service
public class AutoCompleteService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String AC_AP_KEY = "ap";
/**
* input:
* analyzeWorld("黄健宏", 30)
* analyzeWorld("黄健强", 3000)
* analyzeWorld("黄晓朋", 5000)
*
* output:
* dc-10:1>keys ap*
* 1) "ap:黄晓朋"
* 2) "ap:黄晓"
* 3) "ap:he"
* 4) "ap:黄健宏"
* 5) "ap:h"
* 6) "ap:黄健强"
* 7) "ap:黄"
* 8) "ap:黄健"
* @param world
* @param weight
*/
public void analyzeWorld(String world,int weight){
for (int i = 1; i <= world.length(); i++) {
String key = AC_AP_KEY.concat(":").concat(world.substring(0,i));
for (int j = 1; j <= world.length(); j++) {
redisTemplate.opsForZSet().incrementScore(key,world.substring(0,j),weight);
}
}
}
/**
* input:
* 黄健
* output:
* [
* "黄健",
* "黄健宏",
* "黄健强"
* ]
* @param prefix
* @return
*/
public List<String> autoComplete2(String prefix) {
String KEY = AC_AP_KEY.concat(":").concat(prefix);
ZSetOperations zSet = redisTemplate.opsForZSet();
int limit = 5;
Set<String> range = zSet.reverseRange(KEY, 0, limit);
return range.stream().filter(a -> a.startsWith(prefix)).sorted().collect(Collectors.toList());
}
}
controller
/**
* 自动提示
*
* @param prefix
* @return
*/
@GetMapping("/auto/cmp2")
public List<String> autoComplete2(@RequestParam String prefix) {
return autoCompleteService.autoComplete2(prefix);
}
/**
* 分词
*
* @param world
* @param weight
* @return
*/
@GetMapping("/auto/analyze")
public boolean analyze(@RequestParam String world, @RequestParam int weight) {
autoCompleteService.analyzeWorld(world, weight);
return true;
}
测试:
analyze("黄健宏", 30)
analyze("黄健强", 3000)
analyze("黄晓朋", 5000)
查找: http://localhost:8080/auto/cmp2?prefix=黄健
:
[
"黄健",
"黄健宏",
"黄健强"
]