java实现短视频推荐打散算法

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

“上次写的思路还在上次的文章中”

之前写了短视频推荐打散算法的思路:【图解】短视频推荐打散算法
,为了保证推荐视频的层次感,不让相同作者的视频堆积在一起,使用springboot+java简单的实现了单轮询打散算法。

以下只是自己进行打散的逻辑,可能有一堆坑😓,只是个实现思路。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
java复制代码@Slf4j
@Service
public class FirmVideoService {

@Autowired
private RedisList redisList;
@Autowired
private RedisHash redisHash;
@Autowired
private FixSortMapper fixSortMapper;


/**
* 查询推荐视频ID列表
* @param videoId 游标:视频ID,默认从头(0)开始。视频游标可以每次获取视频时都能拿到还未看过的视
频。可以解决分页查询视频时,会出现新增视频,旧的视频被挤到下一页,导致重复视频问题。
* @param isFix 判断是否插入固定视频
* @param size 每页大小
*/
public List<VideoInfo> queryVideo(Integer videoId,int size,boolean isFix) {
List<VideoInfo> videos = new ArrayList<>();
LinkedHashSet dataSet = new LinkedHashSet<>();
// 查询视频id对应作者id
Map<Object, Object> objectMap = redisHash.hmGet("authors");

// 将固定视频放入集合中
List<Integer> fixList = new ArrayList<>();
LinkedHashSet<Integer> authors = new LinkedHashSet<>();

// videoId默认值或有固定标识,就优先显示固定视频
if(0 == videoId || isFix){
this.queryfixIds(fixList,objectMap,authors,size);
}else{
Integer[] fixIds = this.initVideo(size);
fixList = Arrays.asList(fixIds);
}

// 查询计算后的视频id集合
dataSet.addAll(this.queryFirmByRedis(videoId,size));

// 构建结果集
List<Integer> result = this.buildData(dataSet,fixList,authors,objectMap);
result.forEach(s->{
VideoInfo videoInfo = new VideoInfo();
videoInfo.setVideoId(s.longValue());
videos.add(videoInfo);
});

return videos;
}

/**
* 查询固定视频id
* @param fixList 视频ID列表
* @param objectMap 视频作者对应关系
* @param authors 作者记录set
* @param size 每页大小
*/
private void queryfixIds(List<Integer> fixList,Map<Object, Object> objectMap, LinkedHashSet<Integer> authors,int size){
Integer[] fixIds = this.initVideo(size);
this.addFixSorts(fixIds);
fixList.addAll(Arrays.asList(fixIds));

// 将固定视频放入结果集,并记录作者信息
fixList.forEach(h->{
AtomicInteger userIdAto = new AtomicInteger(0);
Optional.ofNullable(objectMap.get(h.toString())).ifPresent(u->{
userIdAto.set(Integer.parseInt(objectMap.get(h.toString()).toString()));
});
int authorId = userIdAto.get();
// 记录作者信息
authors.add(authorId);
});
}

/**
* 查询计算后的视频
* @param videoId 上次看的视频ID
* @param size 每页大小
* @return
*/
private List queryFirmByRedis(Integer videoId,int size){
// 查询全部视频id
Object msi = redisList.lRange("videos",0,-1);

try {
Optional.ofNullable(msi).orElseThrow(()->new Exception("firm video is null"));
} catch (Exception e) {
log.error("firm video is null");
return new ArrayList();
}
ArrayList videoList = JSON.parseObject(msi.toString(), ArrayList.class);

int index = 1;
if(0 != videoId){
// 获取上次查看视频的索引
index += videoList.indexOf(videoId);
}

// 分页获取视频列表
return videoList.subList(index,index+size);
}


/**
* 固定热门视频添加
* @param fixIds 返回视频ID列表
*/
private void addFixSorts(Integer[] fixIds){
// 配置强插,则其余视频按照优先级取余下视频,补足到 4 个
List<FixSorts> fixSorts = fixSortMapper.select();

// 强插数据不为空,顶替原视频
if(!CollectionUtils.isEmpty(fixSorts)){
fixSorts.forEach(h->{
int index = h.getMediafixSort().intValue() - 1;
int videoId = h.getTargetId().intValue();
fixIds[index] = videoId;
});
}
}

/**
* 递归构建视频列表
* @param unused 未放到结果集中的视频ID
* @param result 结果列表
* @param tmp 临时表,保存作者ID
* @param objectMap 视频对应作者映射
* @return
*/
private List<Integer> buildData(LinkedHashSet<Integer> unused,List<Integer> result,LinkedHashSet<Integer> tmp,Map<Object, Object> objectMap){

// 只要有未放入结果集的视频就继续走插入逻辑
Optional.ofNullable(unused).filter(u->u.size()>0).ifPresent(s->{
s.forEach(videoId->{
AtomicInteger userIdAto = new AtomicInteger(0);
// 根据videoId查找作者ID
Optional.ofNullable(objectMap.get(videoId.toString())).ifPresent(u->{
userIdAto.set(Integer.parseInt(objectMap.get(videoId.toString()).toString()));
});
int userId = userIdAto.get();
// 判断是否这次作者ID没有出现过
if(!tmp.contains(userId)){
// 往无数据的索引中插入视频id
for (int i = 0; i < result.size(); i++) {
if(0 == result.get(i)){
result.set(i,videoId);
tmp.add(userId);
break;
}
}
}
});
if(tmp.isEmpty()){
return;
}
// 清空作者信息
tmp.clear();
log.info("buildData result:{}",result);

// 过滤,只保留还未放到结果集中的id
LinkedHashSet<Integer> next = unused.stream().filter(e->!result.contains(e)).collect(Collectors.toCollection(LinkedHashSet::new));
// 继续下一次放入结果
buildData(next,result,tmp,objectMap);
});
return result;
}

/**
* 初始化列表
* @param size 每页大小
* @return
*/
private Integer[] initVideo(int size){
Integer[] arr = new Integer[size];
for (int i = 0; i < size; i++) {
arr[i] = 0;
}
return arr;
}

}

代码逻辑解释

queryVideo是获取推荐视频的入口方法,排序好的视频id列表(videos)和视频与作者id(authors)关系映射缓存在redis中。

首先判断是否显示固定位的视频,如果显示,就将fixList结果列表中对应槽放入固定位视频,并记录作者在authors集合中。

查询出全部准备推荐的视频,放到集合dataSet中。

准备好数据后进入buildData中递归将未放到结果集中的视频ID放入结果集,递归退出条件是没有临时记录的作者信息tmptmp为空就意味着result已经满了。

参考

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%