1. 程式人生 > >JAVA實現仿微信紅包分配規則

JAVA實現仿微信紅包分配規則

個人部落格站已經上線了,網址 www.llwjy.com ~歡迎各位吐槽~

-------------------------------------------------------------------------------------------------

      最近過年發紅包拜年成為一種新的潮流,作為程式猿對演算法的好奇遠遠要大於對紅包的好奇,這裡介紹一種自己想到的一種隨機紅包分配策略,還請大家多多指教。

演算法介紹

一、紅包金額限制

      對於微信紅包,我們知道沒人隨機的最小紅包是1分,最大金額是200元,這裡我們同樣來設定紅包的範圍,下面程式碼我們統一金錢的單位為分。

//最小紅包額度
private static final int MINMONEY = 1;
//最大紅包額度
private static final int MAXMONEY = 200 * 100;

二、判斷紅包金額是否合法

      注意這一步伴隨著整個演算法,我們不僅要在分配紅包之前要判斷金額是否合法,同樣要在每個人暫定隨機金額後也要判斷剩餘的金額是否合法。

private boolean isRight(int money, int count) {
	double avg = money / count;
	if (avg < MINMONEY) {
		return false;
	}
	if (avg > MAXMONEY) {
		return false;
	}
	return true;
}

三、隨機產生一個紅包

      這裡我們採用隨機的方式產生一個在MINMONEY和MAXMONEY之間的一個紅包,產生紅包之後,我們需要判斷剩餘的錢是否是合法紅包,如果不是合法紅包,我們就重新產生分配方案,在重新產生分配方案的時候,我們需要確定一個事情,是產生的紅包過大還是過小,如果紅包過大,下次就隨機一個小值到本次紅包金額的一個紅包,如果紅包金額過小,我們就產生一個紅包金額到大值的一個紅包。

private int random(int money, int minS, int maxS, int count) {
	//紅包數量為1,直接返回金額
	if (count == 1) {
		return money;
	}
	//如果最大金額和最小金額相等,直接返回金額
	if (minS == maxS) {
		return minS;
	}
	int max = maxS > money ? money : maxS;
	//隨機產生一個紅包
	int one = ((int)Math.rint(Math.random() * (max - minS) + minS))  % max + 1;
	int money1 = money - one;
	//判斷該種分配方案是否正確
	if (isRight(money1, count -1)) {
		return one;
	} else {
		double avg = money1 / (count - 1);
		if (avg < MINMONEY) {
			//遞迴呼叫,修改紅包最大金額
			return random(money, minS, one, count);
		}else if (avg > MAXMONEY) {
			//遞迴呼叫,修改紅包最小金額
			return random(money, one, maxS, count);
		}
	}
	return one;
}

第三步優化

      首先感謝子冬童鞋給我提供的優化方案:在隨機產生紅包的時候,我們知道隨機一個紅包後,剩餘的紅包個數,這時我們又知道紅包的最大值、最小值,那麼我們就知道了剩餘金額需要在一個範圍內,因此也就知道了這個產生的隨機紅包的最值,所以每次產生下一個紅包只需要一次隨機即可。舉個簡單的例子,加入現在有一個5分4個的紅包,每個紅包要求至少1分,最大20000分,那麼在分出第一個紅包後,剩餘3個紅包的錢數要在1 * 3 = 3(分) 和 20000 * 3 = 60000(分)之間,因此也就是說,分配第一個紅包的隨機金額要在5 - 60000 = -59995(分)和 5 - 3 = 2(分)之間,又由於我們對紅包的金額要求在1~20000之間,因此確定第一次隨機金額的範圍是[1分,2分]。這種隨機產生紅包的方案要遠遠優於第三步中提供的方案,在此再次感謝子冬同學提供的優化方案。我們只需要將下述方法替換原始碼中的方法即可實現紅包隨機優化。

	private int random(int money, int minS, int maxS, int count) {
		//紅包數量為1,直接返回金額
		if (count == 1) {
			return money;
		}
		//如果最大金額和最小金額相等,直接返回金額
		if (minS == maxS) {
			return minS;
		}
		int max = maxS > money ? money : maxS;
		//分配紅包正確情況,允許紅包的最大值
		int maxY = money - (count - 1) * minS;
		//分配紅包正確情況,允許紅包的最小值
		int minY = money - (count - 1) * maxS;
		//隨機產生紅包的最小值
		int min = minY > minS ? minY : minS;
		//隨機產生紅包最大值
		max = maxY < max ? maxY : max;
		//隨機產生一個紅包
		return (int)Math.rint(Math.random() * (max - min) + min);
	}


四、實現紅包分配

      這裡為了避免某一個紅包占用大量資金,我們需要設定非最後一個紅包的最大金額,我們把他設定為紅包金額平均值的N倍;有了一、二、三中的方法,我們就可以來實現紅包的分配了。

//每個紅包最大是平均值的倍數
private static final double TIMES = 2.1;   

public List<Integer> splitRedPackets(int money, int count) {
	if (!isRight(money, count)) {
		return null;
	}
	List<Integer> list = new ArrayList<Integer>();
	//紅包最大金額為平均金額的TIMES倍
	int max = (int) (money * TIMES / count);
	max = max > MAXMONEY ? MAXMONEY : max;
	for (int i = 0; i < count; i++) {
		int one = random(money, MINMONEY, max, count - i);
		list.add(one);
		money -= one;
	}
	return list;
}


紅包分配方案評估

      上面介紹了紅包的基本演算法,下面我們就對演算法進行一次驗證,假設有一個200元100份的紅包,我們來看一下最後的分配方案。

img


完整程式碼

 /**  
 *@Description:     
 */ 
package com.lulei.weixin.util;  

import java.util.ArrayList;
import java.util.List;

import com.lulei.util.JsonUtil;
  
public class RedPacketUtil {
	//最小紅包額度
	private static final int MINMONEY = 1;
	//最大紅包額度
	private static final int MAXMONEY = 200 * 100;
	//每個紅包最大是平均值的倍數
	private static final double TIMES = 2.1;
	
	/**
	 * @param money
	 * @param count
	 * @return
	 * @Author:lulei  
	 * @Description: 拆分紅包
	 */
	public List<Integer> splitRedPackets(int money, int count) {
		if (!isRight(money, count)) {
			return null;
		}
		List<Integer> list = new ArrayList<Integer>();
		//紅包最大金額為平均金額的TIMES倍
		int max = (int) (money * TIMES / count);
		max = max > MAXMONEY ? MAXMONEY : max;
		for (int i = 0; i < count; i++) {
			int one = random(money, MINMONEY, max, count - i);
			list.add(one);
			money -= one;
		}
		return list;
	}
	
	/**
	 * @param money
	 * @param minS
	 * @param maxS
	 * @param count
	 * @return
	 * @Author:lulei  
	 * @Description: 隨機紅包額度
	 */
	private int random(int money, int minS, int maxS, int count) {
		//紅包數量為1,直接返回金額
		if (count == 1) {
			return money;
		}
		//如果最大金額和最小金額相等,直接返回金額
		if (minS == maxS) {
			return minS;
		}
		int max = maxS > money ? money : maxS;
		//隨機產生一個紅包
		int one = ((int)Math.rint(Math.random() * (max - minS) + minS))  % max + 1;
		int money1 = money - one;
		//判斷該種分配方案是否正確
		if (isRight(money1, count -1)) {
			return one;
		} else {
			double avg = money1 / (count - 1);
			if (avg < MINMONEY) {
				//遞迴呼叫,修改紅包最大金額
				return random(money, minS, one, count);
			}else if (avg > MAXMONEY) {
				//遞迴呼叫,修改紅包最小金額
				return random(money, one, maxS, count);
			}
		}
		return one;
	}
	
	/**
	 * @param money
	 * @param count
	 * @return
	 * @Author:lulei  
	 * @Description: 此種紅包是否合法
	 */
	private boolean isRight(int money, int count) {
		double avg = money / count;
		if (avg < MINMONEY) {
			return false;
		}
		if (avg > MAXMONEY) {
			return false;
		}
		return true;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub  
		RedPacketUtil util = new RedPacketUtil();
		System.out.println(JsonUtil.parseJson(util.splitRedPackets(20000, 100)));
	}
}


-------------------------------------------------------------------------------------------------
小福利
-------------------------------------------------------------------------------------------------
      個人在極客學院上《Lucene案例開發》課程已經上線了,歡迎大家吐槽~