随机匹配
目的
为了帮大家更快地发现和自己兴趣相同的朋友
问题
匹配 1 个还是匹配多个?
答:匹配多个,并且按照匹配的相似度从高到低排序
怎么匹配?(根据什么匹配)
答:标签 tags
还可以根据 user_team 匹配加入相同队伍的用户
本质:找到有相似标签的用户
举例:
用户 A:[Java, 大一, 男]
用户 B:[Java, 大二, 男]
用户 C:[Python, 大二, 女]
用户 D:[Java, 大一, 女]
1. 怎么匹配
-
找到有共同标签最多的用户(TopN)
-
共同标签越多,分数越高,越排在前面
-
如果没有匹配的用户,随机推荐几个(降级方案)
2. 怎么对所有用户匹配,取 TOP
直接取出所有用户,依次和当前用户计算分数,取 TOP N
优化方法:
- 切忌不要在数据量大的时候循环输出日志(取消掉日志后 20 秒)
- Map 存了所有的分数信息,占用内存解决:
维护一个固定长度的有序集合(sortedSet),只保留分数最高的几个用户(时间换空间)
e.g : 【3, 4, 5, 6, 7】取 TOP 5,id 为 1 的用户就不用放进去了 - 细节:剔除自己 √
- 尽量只查需要的数据:
a. 过滤掉标签为空的用户 √
b. 根据部分标签取用户(前提是能区分出来哪个标签比较重要)
c. 只查需要的数据(比如 id 和 tags) √(7.0s) - 提前查?(定时任务)
- 提前把所有用户给缓存(不适用于经常更新的数据)
- 提前运算出来结果,缓存(针对一些重点用户,提前缓存)
大数据推荐
比如说有几亿个商品,难道要查出来所有的商品?
难道要对所有的数据计算一遍相似度?
检索 => 召回 => 粗排 => 精排 => 重排序等等
检索:尽可能多地查符合要求的数据(比如按记录查)
召回:查询可能要用到的数据(不做运算)
粗排:粗略排序,简单地运算(运算相对轻量)
精排:精细排序,确定固定排位
分表学习建议
- mycat框架
- sharding sphere 框架
- 一致性hash
随机匹配实现
编辑距离算法:
https://blog.csdn.net/DBC_121/article/details/104198838
最小编辑距离:字符串 str1 通过最少多少次增删改字符的操作可以变成字符串str2
! 没学过,不要紧,直接当成黑盒导入使用即可
余弦相似度算法:
https://blog.csdn.net/m0_55613022/article/details/125683937(如果需要带权重计算,比如学什么方向最重要,性别相对次要)
后端
引入工具类
新建工具类
cv 代码过来
简单测试一下
之前都是传入字符串,而实际需要比较的是两组字符数组
测试一下,是可以的
取出所有用户,依次和当前用户计算分数
bug : 笑死,为什么这里打印出来和数据库不一样
解决
使用这段代码成功
for (int i = 0; i <userList.size(); i++) {
User user = userList.get(i);
String userTags = user.getTags();
//无标签的
if (StringUtils.isBlank(userTags)){
continue;
}
List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
}.getType());
//计算分数
int distance = AlgorithmUtils.minDistance(tagList, userTagList);
indexDistanceMap.put(i,distance);
}
使用这段代码失败
明明一样
for (int i = 0; i < userList.size(); i++) {
User user=userList.get(i);
String userTags=user.getTags();
//判断用户是否有标签列表
if(StringUtils.isBlank(userTags))
{
continue;
}
//将tags字符串转换为List
List<String> userTagList=gson.fromJson(tags,new TypeToken<List<String>>(){
}.getType());
//计算分数(分数越低,相似度越高)
int distance=AlgorithmUtils.minDistance(tagList,userTagList);
indexDistanceMap.put(i,distance);
}
完整代码
public List<User> matchUsers(int num, User loginUser) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNotNull("tags");
queryWrapper.select("id","tags");
//1.查询所有用户
List<User> userList=this.list(queryWrapper);
//2.获取当前登录用户的标签
//todo 前端传来可能是多个标签,这里是以字符串形式传递的吗?????
String tags=loginUser.getTags();
Gson gson=new Gson();
//将字符串转换为List
List<String> tagList=gson.fromJson(tags,new TypeToken<List<String>>(){
}.getType());
System.out.println("当前登录用户的tagList : "+tagList);
//用户列表的下标:相似度
List<Pair<User,Integer>> list=new ArrayList<>();
//3.遍历用户列表,获得相似度分数
for (int i = 0; i < userList.size(); i++) {
User user=userList.get(i);
String userTags=user.getTags();
//判断无标签或者当前user是登录用户
if(StringUtils.isBlank(userTags) || loginUser.getId().equals(user.getId()))
{
continue;
}
//将tags字符串转换为List
List<String> userTagList=gson.fromJson(userTags,new TypeToken<List<String>>(){
}.getType());
//计算分数(分数越低,相似度越高)
int distance=AlgorithmUtils.minDistance(tagList,userTagList);
list.add(new Pair<>(user,distance));
}
//4.按照编辑距离由小到大排序
List<Pair<User, Integer>> topUserPairList = list.stream()
.sorted((a, b) ->(int) (a.getValue() - b.getValue()))
.limit(num)
.collect(Collectors.toList());
//有顺序的userID列表
List<Integer> userListVo = topUserPairList.stream().map(pari -> pari.getKey().getId()).collect(Collectors.toList());
//根据id查询user完整信息
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.in("id",userListVo);
Map<Integer, List<User>> userIdUserListMap = this.list(userQueryWrapper)
.stream()
.map(user -> getSafetyUser(user))
.collect(Collectors.groupingBy(User::getId));
// 因为上面查询打乱了顺序,这里根据上面有序的userID列表赋值
List<User> finalUserList = new ArrayList<>();
for (Integer userId : userListVo){
finalUserList.add(userIdUserListMap.get(userId).get(0));
}
return finalUserList;
}