Skip to content

Instantly share code, notes, and snippets.

@acalism
Last active March 16, 2018 09:20
Show Gist options
  • Save acalism/af731b5863754aab1216b9cfd6107024 to your computer and use it in GitHub Desktop.
Save acalism/af731b5863754aab1216b9cfd6107024 to your computer and use it in GitHub Desktop.
带最小金额的随机红包分配方法
/// 随机红包(仅带最小金额)
///
/// - Parameters:
/// - amount: 总额(分)
/// - slice: 分成几份,至少一份
/// - minimun: 每个红包的最小金额(分),默认1分(这和微信目前业务场景是一致的)
func redPacket(ammount: Int, slice: Int = 1, minimum: Int = 1) throws -> [Int] {
guard minimum > 0, slice > 0, ammount >= minimum * slice else {
throw NSError(domain: "com.tencent.wechat.redpacket", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法按要求分配红包"])
return [ammount]
}
if slice == 1 {
return [ammount]
}
// 插板法:总共 ammount - minimum * slice + 1个空,需插入 slice - 1 块板
// 每个空均可重插,空与空之间可以是0间距
let numberOfSlot = ammount - minimum * slice + 1
// arc4random_uniform 的结果是 less than 给定的参数
let arr = Array(0..<(slice-1)).map({ _ in Int(arc4random_uniform(UInt32(numberOfSlot))) }).sorted()
var result: [Int] = [arr[0] + minimum]
for i in 1..<arr.count {
result.append(arr[i] - arr[i - 1] + minimum)
}
result.append(ammount - result.reduce(0, +))
return result
}
// 测试代码:
print("开始发红包:")
for _ in 0..<100 { // 执行100次
do {
// 这儿的值可随意修改。比如,现在可解读为10块钱,9个红包,每个红包不少于1块钱。
let t = 1000, s = 9, m = 100
let result = try redPacket(ammount: t, slice: s, minimum: m)
let total = result.reduce(0, +)
print(result, total == t)
} catch {
print(error)
}
}
// 带上下限的红包分配方案
// 一次分发
func deliverRedPacket(total: Int, min: Int, max: Int, num: Int) throws -> Int {
guard total > 0, min > 0, max >= min, total >= min * num, total <= max * num else {
throw NSError.init(domain: "redpacket", code: -1, userInfo: nil)
}
guard num > 1 else {
return total
}
let leastLastRedPacket = total - (num - 1) * max
let maxLastRedPacket = total - (num - 1) * min
let low = leastLastRedPacket < min ? min: leastLastRedPacket
var high = maxLastRedPacket > max ? max : maxLastRedPacket
let ave = total / num > 1 ? total / num : 1
if high > 2 * ave {
print(high, ave)
// high = 2 * ave // 不允许单个红包大于剩余红包平均值的2倍
}
return low + Int(arc4random_uniform(UInt32(high - low + 1)))
}
// 整个分发过程
func redPacket(total: Int, min: Int, max: Int, num: Int) throws -> [Int] {
var t = total
var result = [Int]()
for i in 0..<num {
let packet = try deliverRedPacket(total: t, min: min, max: max, num: num - i)
result.append(packet)
t -= packet
}
return result
}
// 测试代码
let t = 200000
let min = 1000
let max = 20000
let num = 50
for _ in 0..<30 {
do {
print(try redPacket(total: t, min: min, max: max, num: num))
} catch {
print(error)
}
}
@acalism
Copy link
Author

acalism commented Jul 5, 2017

目前,微信随机红包不支持设定最小金额(实为1分),我给出一种带最小金额的随机红包分配方法,且分配结果公平公正。 本质是排列组合里的插板法。 仅需19行代码,算法复杂度O(nn),n为红包个数(sorted和reduce是nn的复杂度)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment