我有一个值为24,同时我有四个文本框。如何动态生成四个值,使它们加起来为24?
所有的值必须是整数,不能为负数,并且结果不能是6、6、6、6;它们必须不同,例如:8、2、10、4(但5、6、6、7是可以的)。
我有一个值为24,同时我有四个文本框。如何动态生成四个值,使它们加起来为24?
所有的值必须是整数,不能为负数,并且结果不能是6、6、6、6;它们必须不同,例如:8、2、10、4(但5、6、6、7是可以的)。
func randomNumbers(#count : Int, withSum sum : Int) -> [Int] {
precondition(sum >= count, "`sum` must not be less than `count`")
var diffs : [Int] = []
var last = 0 // last number chosen
var m = UInt32(sum - 1) // remaining # of elements to choose from
var n = UInt32(count - 1) // remaining # of elements to choose
for i in 1 ..< sum {
// Choose this number `i` with probability n/m:
if arc4random_uniform(m) < n {
diffs.append(i - last)
last = i
n--
}
m--
}
diffs.append(sum - last)
return diffs
}
println(randomNumbers(count: 4, withSum: 24))
func differentRandomNumbers(#count : Int, withSum sum : Int) -> [Int] {
precondition(count >= 2, "`count` must be at least 2")
var v : [Int]
do {
v = randomNumbers(count: count, withSum: sum)
} while (!contains(v, { $0 != v[0]} ))
return v
}
这是一个简单的测试。它计算了100万个随机表示,其中7被表示为3个正整数的和,并统计结果的分布。
let set = NSCountedSet()
for i in 1 ... 1_000_000 {
let v = randomNumbers(count: 3, withSum: 7)
set.addObject(v)
}
for (_, v) in enumerate(set) {
let count = set.countForObject(v)
println("\(v as! [Int]) \(count)")
}
结果:
[1, 4, 2] 66786 [1, 5, 1] 67082 [3, 1, 3] 66273 [2, 2, 3] 66808 [2, 3, 2] 66966 [5, 1, 1] 66545 [2, 1, 4] 66381 [1, 3, 3] 67153 [3, 3, 1] 67034 [4, 1, 2] 66423 [3, 2, 2] 66674 [2, 4, 1] 66418 [4, 2, 1] 66292 [1, 1, 5] 66414 [1, 2, 4] 66751
Swift 3更新:
func randomNumbers(count : Int, withSum sum : Int) -> [Int] {
precondition(sum >= count, "`sum` must not be less than `count`")
var diffs : [Int] = []
var last = 0 // last number chosen
var m = UInt32(sum - 1) // remaining # of elements to choose from
var n = UInt32(count - 1) // remaining # of elements to choose
for i in 1 ..< sum {
// Choose this number `i` with probability n/m:
if arc4random_uniform(m) < n {
diffs.append(i - last)
last = i
n -= 1
}
m -= 1
}
diffs.append(sum - last)
return diffs
}
print(randomNumbers(count: 4, withSum: 24))
针对 Swift 4.2(及更高版本),使用统一的随机 API 进行更新:
func randomNumbers(count : Int, withSum sum : Int) -> [Int] {
precondition(sum >= count, "`sum` must not be less than `count`")
var diffs : [Int] = []
var last = 0 // last number chosen
var m = sum - 1 // remaining # of elements to choose from
var n = count - 1 // remaining # of elements to choose
for i in 1 ..< sum {
// Choose this number `i` with probability n/m:
if Int.random(in: 0..<m) < n {
diffs.append(i - last)
last = i
n -= 1
}
m -= 1
}
diffs.append(sum - last)
return diffs
}
针对您提出的问题,可以生成所有可能解的数组,然后随机选择一个解。实际上,有1,770种可能的解决方案。
var solutions = [[Int]]()
for i in 1...21 {
for j in 1...21 {
for k in 1...21 {
let l = 24 - (i + j + k)
if l > 0 && !(i == 6 && j == 6 && k == 6) {
solutions.append([i, j, k, l])
}
}
}
}
// Now generate 20 solutions
for _ in 1...20 {
let rval = Int(arc4random_uniform(UInt32(solutions.count)))
println(solutions[rval])
}
这样做可以避免任何偏见,但需要付出初始设置时间和存储成本。
可通过以下方式进行改进:
(i * 10000 + j * 100 + k)
,以减少存储空间。以下是将每个解决方案作为单个整数存储,并优化循环的解决方案:
var solutions = [Int]()
for i in 1...21 {
for j in 1...22-i {
for k in 1...23-i-j {
if !(i == 6 && j == 6 && k == 6) {
solutions.append(i * 10000 + j * 100 + k)
}
}
}
}
// Now generate 20 solutions
for _ in 1...20 {
let rval = Int(arc4random_uniform(UInt32(solutions.count)))
let solution = solutions[rval]
// unpack the values
let i = solution / 10000
let j = (solution % 10000) / 100
let k = solution % 100
let l = 24 - (i + j + k)
// print the solution
println("\([i, j, k, l])")
}
func getRandomValues(amountOfValues:Int, totalAmount:Int) -> [Int]?{
if amountOfValues < 1{
return nil
}
if totalAmount < 1{
return nil
}
if totalAmount < amountOfValues{
return nil
}
var values:[Int] = []
var valueLeft = totalAmount
for i in 0..<amountOfValues{
if i == amountOfValues - 1{
values.append(valueLeft)
break
}
var value = Int(arc4random_uniform(UInt32(valueLeft - (amountOfValues - i))) + 1)
valueLeft -= value
values.append(value)
}
var shuffledArray:[Int] = []
for i in 0..<values.count {
var rnd = Int(arc4random_uniform(UInt32(values.count)))
shuffledArray.append(values[rnd])
values.removeAtIndex(rnd)
}
return shuffledArray
}
getRandomValues(4, 24)
这不是最终答案,但应该是一个(好的)起点。
它需要两个参数。随机值的数量(在您的情况下为4)和总量(在您的情况下为24)。
它从总金额到0之间取一个随机值,将其存储在数组中,并将其从存储剩余金额的变量中减去并存储新值。
然后它从剩余的金额到0之间再次取一个新的随机值,将其存储在数组中,并将其从剩余的金额中再次减去并存储新值。
当需要最后一个数字时,它查看剩余的金额并将其添加到数组中。
编辑:
将随机值加上+1
可消除在数组中有0
的问题。
编辑2:
打乱数组可以消除第一个值为高值的概率增加的问题。
以下是一种不幸的非确定性但完全随机的解决方案:
对于四个数字中的总和24:
在1到21之间选择四个随机数字
重复此过程,直到数字的总和等于24且它们不全为6。
平均而言,这将循环约100次才能找到解决方案。
func getRandomValues(amount: Int, total: Int) -> [Int] {
if amount == 1 { return [total] }
if amount == total { return Array(count: amount, repeatedValue: 1) }
let number = Int(arc4random()) % (total - amount + 1) + 1
return [number] + getRandomValues(amount - 1, total - number)
}
并带有安全检查:
func getRandomValues(amount: Int, total: Int) -> [Int]? {
if !(1...total ~= amount) { return nil }
if amount == 1 { return [total] }
if amount == total { return Array(count: amount, repeatedValue: 1) }
let number = Int(arc4random()) % (total - amount + 1) + 1
return [number] + getRandomValues(amount - 1, total - number)!
}
正如@MartinR所指出的那样,上面的代码极其有偏差。因此,为了获得输出值的均匀分布,您应该使用以下代码:
func getRandomValues(amount: Int, total: Int) -> [Int] {
var numberSet = Set<Int>()
// add splitting points to numberSet
for _ in 1...amount - 1 {
var number = Int(arc4random()) % (total - 1) + 1
while numberSet.contains(number) {
number = Int(arc4random()) % (total - 1) + 1
}
numberSet.insert(number)
}
// sort numberSet and return the differences between the splitting points
let sortedArray = (Array(numberSet) + [0, total]).sort()
return sortedArray.enumerate().flatMap{
indexElement in
if indexElement.index == amount { return nil }
return sortedArray[indexElement.index + 1] - indexElement.element
}
}
getRandomValues(3, 7)
产生 [5, 1, 1]
的频率比 [1, 1, 5]
高得多。 - Martin R[5, 1, 1]
也比(例如)[1, 3, 3]
或[1, 2, 4]
更经常返回。您可以使用我的答案末尾的测试代码进行验证。 - Martin Rfunc getRandomDoubles(#count: Int, #total: Double) -> [Double] {
var nonNormalized = [Double]()
nonNormalized.reserveCapacity(count)
for i in 0..<count {
nonNormalized.append(Double(arc4random()) / 0xFFFFFFFF)
}
let nonNormalizedSum = reduce(nonNormalized, 0) { $0 + $1 }
let normalized = nonNormalized.map { $0 * total / nonNormalizedSum }
return normalized
}
func getRandomInts(#count: Int, #total: Int) -> [Int] {
let doubles = getRandomDoubles(count: count, total: Double(total))
var ints = [Int]()
ints.reserveCapacity(count)
for double in doubles {
if double < 1 || double % 1 >= 0.5 {
// round up
ints.append(Int(ceil(double)))
} else {
// round down
ints.append(Int(floor(double)))
}
}
let roundingErrors = total - (reduce(ints, 0) { $0 + $1 })
let directionToAdjust: Int = roundingErrors > 0 ? 1 : -1
var corrections = abs(roundingErrors)
while corrections > 0 {
let index = Int(arc4random_uniform(UInt32(count)))
if directionToAdjust == -1 && ints[index] <= 1 { continue }
ints[index] += directionToAdjust
corrections--
}
return ints
}
*编辑: Martin R 正确指出,这并不像人们期望的那样统一,实际上高度偏向于 1-24 范围中间的数字。我不建议使用这个解决方案,但我仍然保留它,以便其他人知道不要犯同样的错误。
getRandomInts(count: 3, total: 5)
进行了 100,000 次调用,结果似乎分布不均匀。虽然我不是这方面的专家,但根据https://dev59.com/iWsz5IYBdhLWcg3wNE1q#8068956所述,选择一个随机数向量,然后将其缩放到所需的总和 不是 一个好的算法。 - Martin Rand()
。如果那是"C"库函数(看起来是这样),那么就存在一个主要问题,因为rand()
不会产生随机数。正在做的事情与本问题无关。它是将1000万个数字缩小到总和为1.0。我们正在取24个范围内的4个数字。 - zaphrand()
来自MATLAB,它创建了1000万对范围在0.0到1.0之间的随机数。然后每个对都被减少到总和=1.0。 - Martin Rconst numbersSumTo = (length, value) => {
const fourRandomNumbers = Array.from({ length: length }, () => Math.floor(Math.random() * 6) + 1);
const res = fourRandomNumbers.map(num => (num / fourRandomNumbers.reduce((a, b) => a + b, 0)) * value).map(num => Math.trunc(num));
res[0] += Math.abs(res.reduce((a, b) => a + b, 0) - value);
return res;
}
// Gets an array with 4 items which sum to 100
const res = numbersSumTo(4, 100);
const resSum = res.reduce((a, b) => a + b, 0);
console.log({
res,
resSum
});
此外,关于这个问题还可以找到很多不同的方法:https://math.stackexchange.com/questions/1276206/method-of-generating-random-numbers-that-sum-to-100-is-this-truly-random