加权随机的JAVA实现

针对壁纸小程序中的壁纸展示问题,采用加权随机算法确保热门壁纸出现频率更高。通过设计权重计算模型,结合壁纸的特性(如收藏次数)计算权重,并使用TreeMap进行排序,最终实现按权重随机展示壁纸。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、需求分析

我们最近做了个小程序叫「Soda壁纸」,这个小程序的首页会随机全屏显示一张壁纸,点一下刷新按钮就换一张,再点再换,很适合打发时间。

有一天,产品发话了:“这个随机刷新应该多展示受欢迎的壁纸,少展示较冷门的壁纸。”

这句话要这么理解:

现状:随机

比方说我们有两张壁纸

现在要从中抽一张图片,就跟扔硬币一样,正面就壁纸A,反面就壁纸B,概率各50%。

int random = (int)(Math.random()+1);
if(random == 1){
	//壁纸A
} else {
	//壁纸B
}

目标:加权随机

我们还是有两张壁纸,但是壁纸A的妹子更萌一些,产品希望刷新时更大概率刷出壁纸A。怎么个大概率法呢?就让壁纸A的概率是壁纸B的5倍吧。

虽然还是两张图片,但这个时候就不能扔硬币了。两张图片的权重加起来是6,可以扔骰子。扔到1~5是壁纸A,扔到6是壁纸B。概率分别为83.3%和16.7%。

int random = (int)(Math.random()*5+1);
if(random >= 1 && random <= 5){
	//壁纸A
} else {
	//壁纸B
}

加权就这么简单。

2、代码实现

代码实现大概分为几个步骤:

  1. 计算每张壁纸的权重
    1. 取得权重计算所需要的全局因子
    2. 取得每张壁纸需参与权重计算的因子
    3. 计算权重
  2. 根据权重重新分配每张壁纸对应的值域
  3. 获取一个随机数,随机数落在哪张壁纸的值域中,就取哪一张壁纸

首先,设计两个DTO用于封装全局计算因子和壁纸计算因子

/**
 * 图片权重计算全局因子
 */
public class PictureRankCommonFactor {
	// 例如壁纸平均收藏次数
	// 例如活跃用户数
}

/**
 * 单张图片参与权重计算的因子
 */
public class PictureRankFactor {
	private String id; //壁纸Id
	// 例如壁纸的收藏次数
	// 例如壁纸的发布时间
}

然后,设计权重计算器

/**
 * 定义计算器的接口
 */
public interface RankCalculater {
	Double calculater(PictureRankFactor prFactor);
}

/**
 * 实现计算器的接口
 */
public class PictureRankCalculater implements RankCalculater {

	private PictureRankCommonFactor prcFactor; // 全局计算因子
	public void set PictureRankCommonFactor(PictureRankCommonFactor prcFactor) {
		this.prcFactor = prcFactor;
	}

	public Double calculater(PictureRankFactor prFactor){
		// 具体实现
	}
}

再设计一个单例,用来存放排好序的TreeMap

public enum PictureRankTree {
	INSTANCE;

	private TreeMap<Double,String> rankTree;

	public boolean isEmpty() {
		if (null == rankTree || rankTree.isEmpty()) {
			return true;
		} else {
			return false;
		}
	}

	public void setRankTree(TreeMap<Double, String> rankTree) {
		this.rankTree = rankTree;
	}

	private Double maxRank;

	public void setMaxRank(Double maxRank) {
		this.maxRank = maxRank;
	}


	/**
	 * 加权随机获得一个Id
	 * @return
	 */
	public String getRandomId() {
		// 具体实现
	}
}

接下来设计一个定时任务,用来执行各项任务

@Service
@Lazy(value = false)
public class PictureRankScheduler {

	@Scheduled(cron = "0 0 2 * * ?") //每天凌晨2点执行
	public void updatePictureRankMap() {
		// 具体实现
	}
}

好了准备工作做完了,现在开始实现定时任务中的具体代码

public void updatePictureRankMap() {
	/**
	 * 初始化计算器
	 * 1.取出全局因子
	 * 2.创建计算机并传入全局因子
	 */
	PictureRankCommonFactor prCommonFactor = pictureService.getPictureRankCommonFactor();
	RankCalculater calculater = new PictureRankCalculater();
	calculater.setPrCommonFactor(prCommonFactor);

	/**
	 * 计算权重
	 * 1.取出壁纸的计算因子集合
	 * 2.遍历壁纸,进行权重计算,并把结果存入HashMap
	 */
	Map<String,Double> rankMap = Maps.newHashMap(); //这里用HashMap,读写速度快
	List<PictureRankFactor> prFactorList = pictureService.findPRFactorList(picture);
	for (PictureRankFactor prFactor : prFactorList) {
		Double rank = calculater.prCalculater(prFactor);
		rankMap.put(prFactor.getId(),rank);
	}

	/**
	 * 重新分配值域并存入TreeMap
	 * 1.无序取出HashMap中的壁纸Id和权重值
	 * 2.逐个对HashMap中的权重值进行累加,并存入TreeMap进行排序
	 * 以文初的图示为例,壁纸A的权重为5,壁纸B的权重为1,重新分配值域:
	 * 壁纸A:[0,5),壁纸B:[5,6) 或 壁纸B:[0,1),壁纸A:[1,6)
	 * 此时取[0,6)的随机数,根据随机数落入的值域来确定图片,概率分布为壁纸A:83.3%,壁纸B:16.7%
	 */
	TreeMap<Double,String> rankTree = Maps.newTreeMap(); //这里用treeMap,排序,比较大小快
	Double lastRank = 0.0d,currentRank;
	Map.Entry<String,Double> current;
	Iterator<Map.Entry<String,Double>> iterator = rankMap.entrySet().iterator();
	while (iterator.hasNext()){
		current = iterator.next();
		currentRank = current.getValue() + lastRank;
		rankTree.put(currentRank, current.getKey());
		lastRank = currentRank;
	}
	PictureRankTree.INSTANCE.setRankTree(rankTree);
	PictureRankTree.INSTANCE.setMaxRank(rankTree.lastKey());
}

最后,实现PictureRankTree中的getRandomId逻辑

public String getRandomId() {
	// 获得一个[0,maxRank)范围内的随机数
	Double randomDouble = Math.random() * maxRank;
	// 取出TreeMap中所有key值大于(不等于)随机数的子集
	SortedMap<Double, String> sm = this.rankTree.tailMap(randomDouble,false);
	if (!sm.isEmpty()) {
		return this.rankTree.get(sm.firstKey()); //子集中key最小的那个对象就是我们要的结果啦
	} else {
		return null;
	}
}

再用一张图片来说明下

  1. 举个栗子,先取得一个随机数6,在图中坐标轴的箭头1所在位置
  2. 在TreeMap中,取得一个key值大于6的子集,即图中箭头2所示的红色区域
  3. 在子集中取得最左边一个元素,即图中箭头3所示的壁纸B(随机数6落在壁纸B的值域中)

 

全文完。

感谢悠然大神电话远程指导。

大家有兴趣的话可以试用一下我们的小程序:

转载于:https://my.oschina.net/mzdbxqh/blog/1028243

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值