需求:
从一定数量中的歌曲中随机选择300首歌曲进行推荐,每首歌设置权重要提高某些歌曲的命中率。
思想:
- 假如n首歌曲总权重是total,将[1,total]范围内所有整数分成n个部分,每首歌占一个闭区间。如:歌曲songs={a,b,c,d},对应权重w={3,1,2,4},那么歌曲a对应[1,3],b对应[4,4],c对应[5,6],d对应[7,10],长度分别是3,1,2,4。左区间是之前所有元素之和加1,右区间是按顺序到此元素所有权重之和。
- 按这样的规律右边界可以用数组{3,4,6,10}表示,pre[i]=pre[i-1]+w[i]。
- 由于pre是递增的所以可以使用二分查找法快速找到元素,时间复杂度为O(logn)
- 核心算法可参照leetcode算法No.528。
实现:
- 将歌曲与权重对应关系加载到redis中
- 组装pre数组
- 利用二分查找法定位随机数所在区间w[i],w[i]对应songs[i]
- 根据命中的歌曲查询对应的权重并将该区间关闭,防止再次命中并不影响剩余歌曲的命中概率。
- 循环选中300首
代码:
//1、歌曲权重对应关系加载--部分
Map<String, String> generaSongMap = new HashMap<>();
List<Map<String, Object>> recommendSongs = slaveRecommendSongsMapper.selectSongWeightByState(1);
for (Map<String, Object> entry : recommendSongs) {
generaSongMap.put(entry.get("sourceID").toString(), entry.get("weight").toString());
}
Map<String, String> songMap = MyJedis.hgetAll(ConstUtil.RedisCache.REDIS_MSUIC_SMART_VIDEO_CACHE, ConstUtil.RedisKey.SONG_RECOMMEND_GENERAL_WEIGHT_POOL);
if(songMap !=null && songMap.size()>0){
Set<String> strings = songMap.keySet();
Long[] songs = new Long[strings.size()];
int k = 0;
for(String id :strings){
songs[k] = Long.valueOf(id);
++k;
}
Integer[] pre = new Integer[songs.length];
pre[0] = Integer.valueOf(songMap.get(songs[0].toString()));
for(int i = 1; i < songs.length; ++i){
pre[i]= pre[i-1] + Integer.valueOf(songMap.get(songs[i].toString()));
}
for(int i = 0;i<count;i++){
int total = pre[pre.length-1]; //总权重
int x = (int) (Math.random()*total) +1;
int preIndex = binarySearch(x, pre);
Long songid = songs[preIndex];
generalSongIds.add(songid.toString());
int weight =Integer.parseInt(songMap.get(songid.toString()));
for(int j = preIndex;j< pre.length;j++){
pre[j] = pre[j]-weight;
}
}
//二分查找法
private int binarySearch(int x,Integer[] pre) {
int low = 0, high = pre.length - 1;
while (low < high) {
int mid = (high - low) / 2 + low;
if (pre[mid] < x) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}