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
183
184
185
186
187
188
189

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
* 红包
* 采用二倍均值算法
* (0.01, (X/Y) *2)
*/
public class RedEnvelope {

// 最小可领取的金额
private BigDecimal minAmount;

// 最佳金额
private BigDecimal bestAmount;

// 总金额
private BigDecimal totalAmount;

// 总领取数量
private Integer number;

// 当前已经领取数量
private Integer currentNumber;

// 默认最小领取金额(单位:元)
private final static BigDecimal MIN_AMOUNT_DEFAULT = BigDecimal.valueOf(0.01);

// 金额领取池
private List<BigDecimal> amountPool;

/**
* 设置最小领取金额
* @param minAmount
*/
public void setMinAmount(BigDecimal minAmount) {
this.minAmount = minAmount;
}


/**
* @param totalAmount 总金额
* @param number 红包数量
*/
public RedEnvelope(BigDecimal totalAmount, Integer number) {
this.totalAmount = totalAmount;
this.number = number;
this.currentNumber = 0;
this.amountPool = new ArrayList<>();
this.minAmount = MIN_AMOUNT_DEFAULT;
this.bestAmount = BigDecimal.ZERO;
}

/**
* 检查红包配置是否正确
* throw RedEnvelopeException
* @return true 正确 false 有误
*/
public void checkConfig() {
// 检查总金额是否有效
if (this.totalAmount == null || this.totalAmount.compareTo(BigDecimal.ZERO) != 1) {
throw new RedEnvelopeException("RedEnvelope total amount must greater than 0");
}
// 检查总数量是否有效
if (this.number == null || this.number.compareTo(0) != 1) {
throw new RedEnvelopeException("RedEnvelope total amount must greater than 0");
}
// 检查最小领取金额是否有效
if (this.minAmount.compareTo(MIN_AMOUNT_DEFAULT) == -1) {
throw new RedEnvelopeException("RedEnvelope min amount must greater than or equal to " + MIN_AMOUNT_DEFAULT);
}
// 检查是否可分配
if (this.minAmount.multiply(BigDecimal.valueOf(this.number)).compareTo(this.totalAmount) == 1) {
throw new RedEnvelopeException("RedEnvelope unable to allocate");
}
}

/**
* 是否可以领取下一个
* @return true 可以领取 false 不可以领取
*/
public Boolean hasNext() {
return this.currentNumber < this.number;
}

private void execute() {
final Random random = new Random();
BigDecimal remainAmount = BigDecimal.valueOf(this.totalAmount.doubleValue());
for (int i = this.number; i > 0; i--) {
BigDecimal amount;
if (i == 1) {
amount = remainAmount;
} else {
// 计算可领取的最大范围值
BigDecimal max = this.totalAmount.divide(BigDecimal.valueOf(this.number), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(2));
max = max.add(MIN_AMOUNT_DEFAULT).subtract(this.minAmount);
// 计算公约
BigDecimal convertRandom = BigDecimal.ONE.divide(this.minAmount, 10, RoundingMode.HALF_UP);
Integer randomAmount = random.nextInt(convertRandom.multiply(max).intValue());
amount = BigDecimal.valueOf(randomAmount).divide(convertRandom, 2, RoundingMode.HALF_DOWN);
amount = BigDecimal.valueOf(Math.max(amount.doubleValue(), this.minAmount.doubleValue()));
}
bestAmount = BigDecimal.valueOf(Math.max(amount.doubleValue(), this.bestAmount.doubleValue()));
// 将领取金额放入金额池
amountPool.add(amount);
// 剩余金额减少
remainAmount = remainAmount.subtract(amount);
}
}

/**
* 获取当前红包信息
*/
public Info getInfo() {
Info info = new Info();
info.bestAmount = this.bestAmount;
info.minAmount = this.minAmount;
info.currentNumber = this.currentNumber;
info.number = this.number;
info.totalAmount = this.totalAmount;
return info;
}

/**
* 领取下一个红包
* throw RedEnvelopeException
* @return 领取到的金额
*/
public BigDecimal next() {
if (amountPool.size() == 0) {
checkConfig();
execute();
}
if (hasNext()) {
BigDecimal nextAmount = amountPool.get(this.currentNumber);
this.currentNumber++;
return nextAmount;
}
throw new RedEnvelopeException("RedEnvelope already end");
}

static class RedEnvelopeException extends RuntimeException {
public RedEnvelopeException(String message) {
super(message);
}
}

static class Info {

private BigDecimal totalAmount;
private Integer number;
private Integer currentNumber;
private BigDecimal minAmount;
private BigDecimal bestAmount;

public BigDecimal getTotalAmount() {
return totalAmount;
}

public Integer getNumber() {
return number;
}

public Integer getCurrentNumber() {
return currentNumber;
}

public BigDecimal getMinAmount() {
return minAmount;
}

public BigDecimal getBestAmount() {
return bestAmount;
}

@Override
public String toString() {
return "Info{" +
"totalAmount=" + totalAmount +
", number=" + number +
", currentNumber=" + currentNumber +
", minAmount=" + minAmount +
", bestAmount=" + bestAmount +
'}';
}
}
}