101
xiaonec 2019-01-21 11:44:55 +08:00
这不就可以微信红包的分配规则么。
|
102
lfzyx 2019-01-21 11:53:13 +08:00
3L 的方法不错
我的想法是取了随机数后,计算随机数总和与 100 的差值,然后把差值分到每个随机数上 |
103
lfzyx 2019-01-21 11:53:49 +08:00
然后把差值平均分到每个随机数上
|
104
menc 2019-01-21 13:04:28 +08:00 6
|
105
pb941129 2019-01-21 13:07:35 +08:00 2
这个序列的自由度(或者叫『秩』)都是 9 了 不会有真正的十个随机数的 洗洗睡吧 不管哪种方法的本质都是先取九个随机数 最后一个数由这九个随机数线性相关地进行确定
|
106
xi2008wang 2019-01-21 13:16:09 +08:00
http://blog.sciencenet.cn/blog-797552-1089280.html
感觉这个方法不错 先取一个随机序列做权重,每个人使用这个权重取一份(为防止取整时,其和超过总和,其基数用 total - n 代替), 分完后计算零头 re = total-sum(e),这个零头是个小整数, 再随机一个位置序列[random.randint(0, n - 1) for i in range(re)],位置站对的拿下一个零头 # -*- coding: utf-8 -*- import random total = 100 n = 10 x = [random.random() for i in range(n)] e = [int(i / sum(x) * (total - n)) + 1 for i in x] #每人留一分,剩余随机分,用四舍五入可能会超过总数 re = total - sum(e) u = [random.randint(0, n - 1) for i in range(re)] #截断取整剩的零头再随机给各人 1 分 for i in range(re): e[u[i]] += 1 print(e) print(sum(e)) |
107
dezhou9 2019-01-21 13:19:42 +08:00 via Android
随机数的定义就是不被预测啊
|
108
wenzhoou 2019-01-21 14:06:56 +08:00 via Android 1
104 楼我想问你每个数字的随机概率都是平均分布的话,那么 10 个数字的和的期望值是多少。
|
109
baicheng10 2019-01-21 14:27:24 +08:00
生成 10 个随机数 N0-N9
Ni = Ni/(∑Ni)*100 |
111
baicheng10 2019-01-21 14:31:51 +08:00
@baicheng10
效率可能有点问题。零头再摇个骰子随便给谁吧 |
112
kethylar 2019-01-21 14:32:30 +08:00
```
last = 0 for nu in sorted([random.uniform(0, 100) for i in range(9)]): print(nu - last) last = nu print(100 - last) ``` |
114
JCZ2MkKb5S8ZX9pq 2019-01-21 14:40:34 +08:00
@menc 我有 100 块钱,给 10 个人随机分,不等于给 10 个人平均分,更不是在金额上的平均。。。
如果要算平均,可以试试每个 index 上的平均,而不是出现的次数。 也就是分 10w 次,第一个人有时候分得多,有时候分得少,那么他平均得到多少,然后第二个人平均得到多少。。。 最终观察下每个 index 上(每个人)的收益情况是否平均。 |
115
qiaoguoqiang 2019-01-21 14:42:30 +08:00
今天剑也不亮了,静静地看大佬们回答
|
116
menc 2019-01-21 14:47:21 +08:00
@JCZ2MkKb5S8ZX9pq
随机不是拍脑门想的,你拍脑门想完了,要证明分布符合预期。 回到这个问题,虽然本身就不是 well defined 的,题目没有定义随机。但是就 3 楼的解法,最后回到幂律分布,大部分数落在[0,1],想必也不是符合预期的。 随机数没有 index 的说法,一组随机数打乱顺序仍然是一组随机数,随机和顺序无关。 |
117
jobscolin 2019-01-21 14:51:24 +08:00
所以问题来了,根据随机数产生的数到底算不算随机数?
|
118
bloodspasm 2019-01-21 14:54:23 +08:00 via iPhone
@Linxing 会大于 100 第一次 51 第二次 52 ?
|
119
kernelG 2019-01-21 15:10:08 +08:00
哪有那么复杂
就随机生成随机数,开区间(0,100) 生成 9 个 如果有重复,就去掉再生成,直到满足九个不同的数字为止 然后按小到大排序 假设排序后这个 9 个数字是 a0,a1,...,a8 那么红包的额度分别为 a0 a1-a0, a2-a1, ... 100-a8 至于楼上说分布的,这个红包的数额本来就不应该是均匀分布,我倒觉得更偏向于二项分布 |
120
zst 2019-01-21 15:13:34 +08:00 via Android
你这只有只有 9 个自由度吧.....
可以考虑生成 100 个 1 然后往其中 99 个空随机加 9 个 0 的玩法 |
121
JCZ2MkKb5S8ZX9pq 2019-01-21 15:15:12 +08:00 1
@menc 哎,你针对的是每个数字出现的次数。。。
我简单算了一下,我之前按 3 楼的思路写的算法 #15,随机的确有点问题。 0 | 9.64671 1 | 10.11338 2 | 10.14475 3 | 10.07507 4 | 10.11595 5 | 10.07307 6 | 10.13353 7 | 10.05408 8 | 10.05961 9 | 9.58385 第一个和最后一个偏低一点,不大完美。我想想怎么修复下。 代码贴出来格式会乱,就不贴了。 |
122
lcatt 2019-01-21 15:36:23 +08:00
@menc 3 楼的方法就是用连续均匀分布产生随机点, 但是随机点的间隔是指数分布。另外对于随机的理解, 不是说最后结果是均匀分布才是随机,正态随机分布也是随机。
|
123
discrete 2019-01-21 15:36:54 +08:00
@menc 常理来说,平均每个人收 10 块左右比较理想。那么收益是 10 以上的概率是多少呢?按照模拟,是 0.4073731 ( 1000000 次模拟),其实足够公平。你看的是 Probability Density Function,看 CDF 比较合适。你总不能期望一个平直的 PDF 吧。
|
124
discrete 2019-01-21 15:39:28 +08:00
https://gist.github.com/alanzchen/f622ad18d631209620212efa708896e0 附上 3 楼的 Python 模拟代码。
|
125
KagurazakaBonzai 2019-01-21 16:02:59 +08:00
import random
import math from random import shuffle def get_numbers(total=100, count=10, regulate=False): top = total numbers = list() for i in range(count-1): if regulate: regulator = math.pow(top, 1/11) / 2 else: regulator = 1 rand_number = random.randint(0, int(top*regulator)) numbers.append(rand_number) top = top - rand_number numbers.append(top) shuffle(numbers) return numbers 如果需要让结果看起来更均匀一点可以把 regulate 打开。 |
126
codingKingKong 2019-01-21 16:07:51 +08:00
```
import java.util.Random; public class Temp { public static void main(String[] args) { int[] t = new int[101]; for (int i = 1; i < 101; i++) { t[i] = i; } int[] r = roll(t); for (int rt : r) { System.out.println(rt); } } private static int[] roll(int[] p){ int[] r = new int[10]; int sum_limit = 100; int cal_time = 10; for (int i = 0; i < cal_time; i++) { int ran = new Random().nextInt(sum_limit + 1); sum_limit -= ran; r[i] = ran; } if (sum_limit > 0) { r[9] = r[9] + sum_limit; } return r; } } ``` 嗯... 瞎写的 |
127
Olorin 2019-01-21 16:44:38 +08:00
不考虑其他因素的简单实现
import random a=[] for x in range(10): if sum(a) <= 100: num=random.randint(0,100-sum(a)) a.append(num) else: a.append(0) print(a) |
128
frazy 2019-01-21 17:45:23 +08:00
相对平均的死算法,可以调整这里的 20 来观察: https://jsfiddle.net/gt5cy91q/5/
` randomInt = (min, max) => { return Math.round(Math.random()*max + min); }; for(var j = 0; j < 100; j++) { let a = [0,0,0,0,0,0,0,0,0,0]; let left = 100; for(;left>0;) { for(var i = 0; i < a.length; i++) { const r = randomInt(0, 20); // 调整这里的 1=[1,100] a[i] += left > r ? r : left; left -= r; if (left <= 0) { break; } } } let total = 0; a.map(n => total += n); console.log(a, total); } ` |
129
geelaw 2019-01-21 18:14:48 +08:00
就一个抽红包算法也能出来这么多幺蛾子,三楼 @celeron533 的方法对于实数的分割是完美正确的,稍加修改即可得到抽红包的算法。
假设要把 N 单位的钱分给 n 个人,每个人至少 1 单位,且是所有可能分法中均匀分布的,则可以用“插板法”来抽样。把 N 单位的钱看成 N 个球,每两个相邻的球之间有一个位置( N - 1 个位置)可以插入隔板,现在只要随机抽取 n-1 个隔板即可按照需要的分布拆成 n 个人分到的红包数。 从 N-1 个元素中选择 n-1 个元素可以用 reservoir sampling,时间复杂度是 O(N)。 ———— 至于楼主觉得自己的问题不是抽红包,这只是一种表面的错觉。 假设要抽取 n 个自然数,使其和为 N,且在所有可能的抽取结果上均匀,这相当于分 N+n 单位的钱的红包给 n 个人——把每个人的数 +1 就得到红包的分法,把红包的分法中每个人的钱 -1 就得到这个拆分。 |
130
love9918 2019-01-21 19:07:22 +08:00
这个既然是随机数,就说明是可以重复的,[0,100]都可以取到,先用 random.choice 选出 10 个数,加到新的列表,然后 sum 求和,如果等于 100,可以把数据重新存入一个列表,不符合返回重新运行直到第一个等于 100 的 10 个数打印出来结束
|
131
chinvo 2019-01-21 19:10:38 +08:00
回头仔细想想,我在前面给的方法完全不对(
|
132
necomancer 2019-01-21 21:24:27 +08:00
三楼方法没毛病,这些数就应该服从幂率分布。
ans = np.diff([0]+list(np.sort(random.randint(0,100,9)))+[100]) f(x1,x2....x10) 的联合分布应该是均匀的也就是 f(x1,...x10)=1/N ( N=100H10,H 是可重复组合的算符)也就是等概率地在所有和等于 100 的 10 个数里找到 10 个数的概率,但是边际分布(你可以理解是 f(x1) f(x2)...)是做不到这种“均匀”的,因为 E[xi]=50.5,E[x1+...x10]=505,这种分布是不存在的。 而边际分布是幂律分布的数才能满足 (x1,...x10) 这样一组 10 个数是被等概率地从 100H10 的空间中抽出。 |
133
necomancer 2019-01-21 21:26:40 +08:00
不过貌似幂律可能是充分非必要吧,我没认真证过,也懒得查书了,也就是应该有其他边际分布存在使得联合分布是 f(x1,x2...)=1/N 这样的均匀分布。
|
134
necomancer 2019-01-21 21:29:31 +08:00
如果情况复杂点比如你要求每个数字应该在 0~10 的范围,那就只能从你想要的联合分布出手,求边际分布然后慢慢采样了……
|
135
canwushuang 2019-01-21 21:34:47 +08:00 via Android
每生成一个随机数 把随机范围减去这个生成的随机数继续生成。
|
136
luofeii 2019-01-21 21:52:14 +08:00 via Android
设置 10 个空心柱子,100 个球每次都随机到这 10 个空心柱子,
|
137
luofeii 2019-01-21 21:54:42 +08:00 via Android
投掷 100 次。或者每次投掷的球数量可以设置为有范围的任意数 n
|
138
ooleslie 2019-01-21 23:38:33 +08:00
你这个命题内在有点矛盾。
要么,前九个数相对随机,第一个数(a0)完全随机,第二个数取值[0,100-a0],但最后一个数不随机 要么,随机生成 10 个数,然后通过 a0+..a9=100 作为判定条件筛选 感觉题目本身有问题 |
139
auciou2 2019-01-22 08:09:54 +08:00 1
补充,60 楼的方法,也许不是最简单的,但是能彻底解决楼主的场景需求,不会出现负数,不会出错。
思路是先列举出所有符合条件的 10 个数相加为 100(不考虑排列顺序为 66 种),这时候时候可以不考虑 10 个数的排列顺序,消耗的服务器资源,循环次数仅为 101*101。 最后的 2 步,先随机拿出一个符合条件的 10 个数的组合。再将拿到的组合,再随机排列。这样,就完美解决“随机”数的需求。 如果数字可重复,程序反而复杂一些,但也能实现。 |
140
auciou2 2019-01-22 08:18:13 +08:00 1
139 楼再补充,列举出的这 66 种,是设计一段程序,让程序自动列举,每一种的值建立一个数组,再从 1-66 随机拿出一个数组。最后,再将拿到的这 10 个数再随机排列大小。假设这 10 个数不重复,如果考虑排列顺序,这些数的种类非常多,也许是 66 的 9 次方,如果 10 个数可重复,那种种类更多。所以,完全满足了随机的需求。
|
141
auciou2 2019-01-22 08:38:46 +08:00
60 楼再补充,60 楼的算法,由于只循环了 101*101 次,所以对服务器资源的消耗很小,消耗的时间约为 0.01 秒以内。这样的程序,适合于不太高频访问的程序。
对于高频访问的程序,可以事先建立一个后台页面,让其自动列举出这 66 种组合。 这样,下次在这个页面使用的时候,直接从 66 种组合中随机拿出一组,再将 10 个数随机排列,这样,使用时完全没有了循环程序,程序效率提高了很多。 |
142
auciou2 2019-01-22 08:39:36 +08:00
让其自动列举出这 66 种组合,存储在数据库里。
|
143
5200 2019-01-22 09:05:31 +08:00
import random
list = [0,0,0,0,0,0,0,0,0,0] for num in range(1,100): list[random.randint(0,9)] +=1 print(list) |
144
moonsola 2019-01-22 09:06:57 +08:00
@menc
结果肯定不是均匀分布的。 由 10 个[0,100]间的数字构成一个数组,所有满足这个条件的数组构成了集合 A,集合 A 中的数字分布肯定是均匀的。 从集合 A 中取出所有满足“ 10 个数之和为 100 ”这个条件的数组,构成集合 B,集合 B 中的数字分布肯定是不均匀的,应该就是幂律分布。 楼主要的效果就是从集合 B 中随机取出一个数组。3 楼的方法可行 |
145
darknoll 2019-01-22 09:25:51 +08:00
这问题很简单啊:
def test(max_num, n): if n == 0: return x = random.randint(0, max_num) print(x) max_num -= x n -= 1 test(max_num, n) |
146
darknoll 2019-01-22 09:26:15 +08:00
调用的时候 test(100, 10)
|
147
meisky6666 2019-01-22 09:28:29 +08:00
10 个随机数范围 1-100,10 个数相加=100 的 print 出来就完了
|
148
neptuno 2019-01-22 09:29:57 +08:00 via Android
@celeron533 牛逼
|
149
akura 2019-01-22 09:30:53 +08:00 via Android
红包算法,3 楼正解
|
150
benmo 2019-01-22 11:32:10 +08:00
@celeron533 的算法对楼主的题目没问题,但如果换一下范围和总和就不一定了吧,比如,范围要求还是[0, 100], 总和要求是 300 呢?如果还按照区间长度 100 划分 10 段,总和显然不是 300,如果按照区间长度是 300 划分 10 段,那么怎么保证每段长度都是[0, 100]?
|
151
benmo 2019-01-22 11:33:59 +08:00
理解楼主的题目,应该是要满足以下条件
|
152
benmo 2019-01-22 11:41:34 +08:00
// 一个 js 实现
const MINN = 0 ; const MAXN = 100; let cnt = 10; let total = 100; let res = []; let l = Math.max(MINN, total - (cnt - 1) * MAXN); let r = Math.min(MAXN, total - (cnt - 1) * MINN); let randomNumN = rangeRandom(l, r); total -= randomNumN; cnt--; res.push(randomNumN); |
153
stevenkang 2019-01-22 11:42:26 +08:00
之前写的一个砍价算法,和你这个需求应该类似。可调参数:先高后低还是先低后高,以及温和还是刺激(根据参与随机比例来实现)
```java public static void main(String[] args) { BigDecimal total = new BigDecimal(100.00); BigDecimal current = new BigDecimal(0.00); int totalPersonal = 10; int currentPersonal = 0; for (int i=0; i<totalPersonal; i++) { BigDecimal amount = exec(total, current, totalPersonal, currentPersonal, 2.0, 0.2); current = current.add(amount); currentPersonal++; System.out.println("当前砍价:" + amount + ",剩余金额:" + (total.subtract(current)) + ",剩余次数:" + (totalPersonal-currentPersonal)); } } /*** * 砍价金额计算程序,可通过 adjust 调整分配比例,越小前期砍价金额越小,越大前期砍价金额越大<br> * 0.5 表示均衡砍价。<br> * stable 稳定参数表示每次砍价参与随机的份额,越小越稳定。0.3 表示砍价的 30% 金额随机,其他固定。<br> * 刚开始砍的金额高,后面低,推荐配置:adjust = 2.0、stable = 0.8 * 刚开始砍的金额低,后面高,推荐配置:adjust = 0.5、stable = 0.8 * @param total 总价 * @param current 已砍金额 * @param totalPersonal 总人数 * @param currentPersonal 已砍人数 * @param adjust 调整值,0.5-2.0 * @param stable 稳定值,0.0-1.0 * @return 本次砍价金额 */ public static BigDecimal exec(BigDecimal total, BigDecimal current, int totalPersonal, int currentPersonal, double adjust, double stable) { double dTotal = total.setScale(20, RoundingMode.HALF_UP).doubleValue(); double dCurrent = current.setScale(20, RoundingMode.HALF_UP).doubleValue(); // 计算平均砍价金额 double avgAmount = dTotal / totalPersonal; // 初始调整值 double initAdjust = adjust; // 总调整值为 1 除以初始调整值于初始调整值的差值 double totalAdjust = 2 - initAdjust * 2; // 每次调整值为差值除以总人数减一 double perAdjust = totalAdjust / totalPersonal; // 本次砍价金额 double amount; if (currentPersonal > 0 && currentPersonal < totalPersonal-1) { // 渐变算法调整计算 double adjustBigDecimal = initAdjust + perAdjust * currentPersonal; amount = Math.random() * avgAmount * adjustBigDecimal * 2 * stable + avgAmount * (1-stable); } else if (currentPersonal == 0) { // 首次砍价使用初始调整值计算 amount = Math.random() * avgAmount * initAdjust * 2 * stable + avgAmount * (1-stable); } else { // 最后砍价使用剩余金额 amount = dTotal - dCurrent; } // 本次砍价后剩余金额 BigDecimal bigAmount = new BigDecimal(amount).setScale(2, RoundingMode.HALF_UP); BigDecimal afterAmount = total.subtract(current).setScale(2, RoundingMode.HALF_UP); afterAmount = afterAmount.subtract(bigAmount).setScale(2, RoundingMode.HALF_UP); // 剩余砍价次数以及保障金额 BigDecimal afterMinAmount = new BigDecimal(0.01F * (totalPersonal-currentPersonal-1)).setScale(2, RoundingMode.HALF_UP); if (afterAmount.compareTo(afterMinAmount) < 0) { bigAmount = new BigDecimal(0.01F).setScale(2, RoundingMode.HALF_UP); } // 最低一分钱 if (bigAmount.compareTo(BigDecimal.ZERO) <= 0) { bigAmount = new BigDecimal(0.01F).setScale(2, RoundingMode.HALF_UP); } return bigAmount; } ``` 效果图( 100 元,10 个人参与) ![]( https://img.xiaoi.me/pms-upload/20190122/5e7f3e86-199d-be8e-fe26-c6d0905a8aae.png) |
154
summerLast 2019-01-22 11:43:38 +08:00
分享一个思路 设置初值为 100 随机产生 0-100 之内的数字 如 88
设置值为 12 随机生成 0-12 如 3 设置值为 9 随机生成 0-9 ... 最后一次的值直接返回 |
155
sine 2019-01-22 13:33:55 +08:00 via iPhone
楼主你好,请看看以下方法如何:
from random import randint def sum_of_rand(sum_to=100,length=10): rand_list = [randint(0,sum_to) for x in range(length)] rand_sum = sum(rand_list) sum_list = [x/rand_sum*sum_to for x in rand_list] return sum_list |
156
accppd 2019-01-22 13:38:05 +08:00 via Android
@celeron533 厉害
|
157
xavierskip 2019-01-22 17:20:09 +08:00
啊,按照直观想法一个一个数字生成呗。每一个会收到前一个随机数字的影响,哪怕最后一个数字不用选,怎么就不随机了呢?
https://gist.github.com/xavierskip/b8151b94ccd0e3eff6fad1aa9e55cd98 |
158
Linxing 2019-01-22 17:22:46 +08:00
@bloodspasm
for count in range(0,num): each_one = int(remain / int(num-(count))) if each_one != 1: if count == (num - 1): #最后一个红包 全拿走 lucky_amount = remain else: if each_one*2 < remain: lucky_amount = random.randint(1, each_one*2-1) remain = remain - lucky_amount else: lucky_amount = random.randint(1, remain-1) remain = remain - lucky_amount else: lucky_amount = each_one remain = remain - lucky_amount redisLpush(key, lucky_amount) |
159
Linxing 2019-01-22 17:24:36 +08:00
代码乱了 反正每次都要判断一下余下的值是否够不够随机
@bloodspasm 判断下余下的数啊 |
160
akira 2019-01-22 17:58:34 +08:00
用 128 楼的代码改的,js 不熟悉,只能改别人的了。。
randomInt = (min, max) => { return Math.round(Math.random()*max + min); }; const SUM = 100; let a = [0,0,0,0,0,0,0,0,0,0]; for(var i = 0; i < SUM; i++) { r = randomInt(0,9); a[r]++; } let total = 0; a.map(n => total += n); console.log(a, total); |
161
mayorbryant 2019-01-22 19:43:39 +08:00
|
162
jiangbingo 2019-01-22 21:52:12 +08:00
@mayorbryant 这个算法算是工程化的 code,可以跑 UT 了。
|
163
mayorbryant 2019-01-23 09:54:01 +08:00
@jiangbingo 可以再优化一下,比如说 py2 的 xrange 和 range, 当人数和金额比较大时,range 会有性能问题,换成 xrange 会快很多,都是秒出
|
164
DiamondbacK 2019-01-23 10:43:05 +08:00 via Android
随机变量应当服从什么分布,本来是问题本身应该给出的条件。问题给不出,回答就肯定一团乱,或者在一个不明确的分布下给出算法,或者变成争论哪种分布才合理(只要问题限定了分布,就仅仅是一个计算条件,不存在合理不合理)。
|
165
woshigehaoren 2019-01-24 18:16:55 +08:00
随机 10 个数,然后把他们加起来,然后按百分比分。
|
166
1150774022 2019-02-15 11:27:27 +08:00
@benmo 随机出来的每个数字乘 3 不就解决了?
|
167
Slyutae 2019-03-08 21:50:24 +08:00 via Android
随机前 9 个数,如果前 9 个数的 sum 大于 100,丢掉,如果小于 100,100-sum 就是第 10 个数
|