首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python 学习手册
Python Cookbook
Python 基础教程
Python Sites
PyPI - Python Package Index
http://www.simple-is-better.com/
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
宝塔
V2EX  ›  Python

请教一个问题,生成 10 个随机数 [0,100] 且最终 10 个随机数之和为 100 , 用 Python 如何实现

  •  1
     
  •   commoccoom · 305 天前 · 14483 次点击
    这是一个创建于 305 天前的主题,其中的信息可能已经有所发展或是发生改变。
    第 1 条附言  ·  305 天前
    感谢大家的帮助,看了大家的回复后感觉确实有点像微信红包的算法,但是红包是不包含 0 的。另外,我也感觉标题中的“随机”这个表述有问题,应该是 [从 [0,100] 取 10 个数,使之和等于 100,可取 0 和 100 ] 不知道这次表述是否合适了。
    第 2 条附言  ·  305 天前
    数字可重复
    第 3 条附言  ·  304 天前
    感谢各位的帮助,我会试试各位给出的方法的。
    167 回复  |  直到 2019-03-08 21:50:24 +08:00
    1  2  
        101
    xiaonec   304 天前
    这不就可以微信红包的分配规则么。
        102
    lfzyx   304 天前
    3L 的方法不错

    我的想法是取了随机数后,计算随机数总和与 100 的差值,然后把差值分到每个随机数上
        103
    lfzyx   304 天前
    然后把差值平均分到每个随机数上
        104
    menc   304 天前   ♥ 6
    @celeron533
    三楼的方法是错的,最终的分布并不是均匀分布。

    我做了 10w 次模拟实验,最后的分布式幂律分布,不是每个数字都会均匀出现的。

        105
    pb941129   304 天前   ♥ 2
    这个序列的自由度(或者叫『秩』)都是 9 了 不会有真正的十个随机数的 洗洗睡吧 不管哪种方法的本质都是先取九个随机数 最后一个数由这九个随机数线性相关地进行确定
        106
    xi2008wang   304 天前
    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   304 天前 via Android
    随机数的定义就是不被预测啊
        108
    wenzhoou   304 天前 via Android   ♥ 1
    104 楼我想问你每个数字的随机概率都是平均分布的话,那么 10 个数字的和的期望值是多少。
        109
    baicheng10   304 天前
    生成 10 个随机数 N0-N9
    Ni = Ni/(∑Ni)*100
        110
    lcatt   304 天前
    @menc 本来就不是均匀分布啊
        111
    baicheng10   304 天前
    @baicheng10
    效率可能有点问题。零头再摇个骰子随便给谁吧
        112
    kethylar   304 天前
    ```
    last = 0
    for nu in sorted([random.uniform(0, 100) for i in range(9)]):
    print(nu - last)
    last = nu
    print(100 - last)
    ```
        113
    menc   304 天前
    @lcatt 那叫什么随机
        114
    JCZ2MkKb5S8ZX9pq   304 天前
    @menc 我有 100 块钱,给 10 个人随机分,不等于给 10 个人平均分,更不是在金额上的平均。。。
    如果要算平均,可以试试每个 index 上的平均,而不是出现的次数。
    也就是分 10w 次,第一个人有时候分得多,有时候分得少,那么他平均得到多少,然后第二个人平均得到多少。。。
    最终观察下每个 index 上(每个人)的收益情况是否平均。
        115
    qiaoguoqiang   304 天前
    今天剑也不亮了,静静地看大佬们回答
        116
    menc   304 天前
    @JCZ2MkKb5S8ZX9pq

    随机不是拍脑门想的,你拍脑门想完了,要证明分布符合预期。
    回到这个问题,虽然本身就不是 well defined 的,题目没有定义随机。但是就 3 楼的解法,最后回到幂律分布,大部分数落在[0,1],想必也不是符合预期的。

    随机数没有 index 的说法,一组随机数打乱顺序仍然是一组随机数,随机和顺序无关。
        117
    jobscolin   304 天前
    所以问题来了,根据随机数产生的数到底算不算随机数?
        118
    bloodspasm   304 天前 via iPhone
    @Linxing 会大于 100 第一次 51 第二次 52 ?
        119
    kernelG   304 天前
    哪有那么复杂
    就随机生成随机数,开区间(0,100)
    生成 9 个
    如果有重复,就去掉再生成,直到满足九个不同的数字为止
    然后按小到大排序
    假设排序后这个 9 个数字是 a0,a1,...,a8
    那么红包的额度分别为

    a0
    a1-a0,
    a2-a1,
    ...
    100-a8

    至于楼上说分布的,这个红包的数额本来就不应该是均匀分布,我倒觉得更偏向于二项分布
        120
    zst   304 天前 via Android
    你这只有只有 9 个自由度吧.....
    可以考虑生成 100 个 1 然后往其中 99 个空随机加 9 个 0 的玩法
        121
    JCZ2MkKb5S8ZX9pq   304 天前   ♥ 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   304 天前
    @menc 3 楼的方法就是用连续均匀分布产生随机点, 但是随机点的间隔是指数分布。另外对于随机的理解, 不是说最后结果是均匀分布才是随机,正态随机分布也是随机。
        123
    discrete   304 天前
    @menc 常理来说,平均每个人收 10 块左右比较理想。那么收益是 10 以上的概率是多少呢?按照模拟,是 0.4073731 ( 1000000 次模拟),其实足够公平。你看的是 Probability Density Function,看 CDF 比较合适。你总不能期望一个平直的 PDF 吧。
        124
    discrete   304 天前
    附上 3 楼的 Python 模拟代码。
        125
    KagurazakaBonzai   304 天前
    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   304 天前
    ```
    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   304 天前
    不考虑其他因素的简单实现
    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   304 天前
    相对平均的死算法,可以调整这里的 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   304 天前
    就一个抽红包算法也能出来这么多幺蛾子,三楼 @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   304 天前
    这个既然是随机数,就说明是可以重复的,[0,100]都可以取到,先用 random.choice 选出 10 个数,加到新的列表,然后 sum 求和,如果等于 100,可以把数据重新存入一个列表,不符合返回重新运行直到第一个等于 100 的 10 个数打印出来结束
        131
    chinvo   304 天前
    回头仔细想想,我在前面给的方法完全不对(
        132
    necomancer   304 天前
    三楼方法没毛病,这些数就应该服从幂率分布。

    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   304 天前
    不过貌似幂律可能是充分非必要吧,我没认真证过,也懒得查书了,也就是应该有其他边际分布存在使得联合分布是 f(x1,x2...)=1/N 这样的均匀分布。
        134
    necomancer   304 天前
    如果情况复杂点比如你要求每个数字应该在 0~10 的范围,那就只能从你想要的联合分布出手,求边际分布然后慢慢采样了……
        135
    canwushuang   304 天前 via Android
    每生成一个随机数 把随机范围减去这个生成的随机数继续生成。
        136
    luofeii   304 天前 via Android
    设置 10 个空心柱子,100 个球每次都随机到这 10 个空心柱子,
        137
    luofeii   304 天前 via Android
    投掷 100 次。或者每次投掷的球数量可以设置为有范围的任意数 n
        138
    ooleslie   304 天前
    你这个命题内在有点矛盾。
    要么,前九个数相对随机,第一个数(a0)完全随机,第二个数取值[0,100-a0],但最后一个数不随机
    要么,随机生成 10 个数,然后通过 a0+..a9=100 作为判定条件筛选

    感觉题目本身有问题
        139
    auciou2   304 天前   ♥ 1
    补充,60 楼的方法,也许不是最简单的,但是能彻底解决楼主的场景需求,不会出现负数,不会出错。
    思路是先列举出所有符合条件的 10 个数相加为 100(不考虑排列顺序为 66 种),这时候时候可以不考虑 10 个数的排列顺序,消耗的服务器资源,循环次数仅为 101*101。

    最后的 2 步,先随机拿出一个符合条件的 10 个数的组合。再将拿到的组合,再随机排列。这样,就完美解决“随机”数的需求。
    如果数字可重复,程序反而复杂一些,但也能实现。
        140
    auciou2   304 天前   ♥ 1
    139 楼再补充,列举出的这 66 种,是设计一段程序,让程序自动列举,每一种的值建立一个数组,再从 1-66 随机拿出一个数组。最后,再将拿到的这 10 个数再随机排列大小。假设这 10 个数不重复,如果考虑排列顺序,这些数的种类非常多,也许是 66 的 9 次方,如果 10 个数可重复,那种种类更多。所以,完全满足了随机的需求。
        141
    auciou2   304 天前
    60 楼再补充,60 楼的算法,由于只循环了 101*101 次,所以对服务器资源的消耗很小,消耗的时间约为 0.01 秒以内。这样的程序,适合于不太高频访问的程序。

    对于高频访问的程序,可以事先建立一个后台页面,让其自动列举出这 66 种组合。
    这样,下次在这个页面使用的时候,直接从 66 种组合中随机拿出一组,再将 10 个数随机排列,这样,使用时完全没有了循环程序,程序效率提高了很多。
        142
    auciou2   304 天前
    让其自动列举出这 66 种组合,存储在数据库里。
        143
    5200   304 天前
    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   304 天前
    @menc
    结果肯定不是均匀分布的。
    由 10 个[0,100]间的数字构成一个数组,所有满足这个条件的数组构成了集合 A,集合 A 中的数字分布肯定是均匀的。
    从集合 A 中取出所有满足“ 10 个数之和为 100 ”这个条件的数组,构成集合 B,集合 B 中的数字分布肯定是不均匀的,应该就是幂律分布。
    楼主要的效果就是从集合 B 中随机取出一个数组。3 楼的方法可行
        145
    darknoll   304 天前
    这问题很简单啊:
    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   304 天前
    调用的时候 test(100, 10)
        147
    meisky6666   304 天前
    10 个随机数范围 1-100,10 个数相加=100 的 print 出来就完了
        148
    neptuno   304 天前 via Android
    @celeron533 牛逼
        149
    akura   304 天前 via Android
    红包算法,3 楼正解
        150
    benmo   303 天前
    @celeron533 的算法对楼主的题目没问题,但如果换一下范围和总和就不一定了吧,比如,范围要求还是[0, 100], 总和要求是 300 呢?如果还按照区间长度 100 划分 10 段,总和显然不是 300,如果按照区间长度是 300 划分 10 段,那么怎么保证每段长度都是[0, 100]?
        151
    benmo   303 天前
    理解楼主的题目,应该是要满足以下条件
        152
    benmo   303 天前
    // 一个 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   303 天前
    之前写的一个砍价算法,和你这个需求应该类似。可调参数:先高后低还是先低后高,以及温和还是刺激(根据参与随机比例来实现)

    ```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   303 天前
    分享一个思路 设置初值为 100 随机产生 0-100 之内的数字 如 88
    设置值为 12 随机生成 0-12 如 3
    设置值为 9 随机生成 0-9
    ...
    最后一次的值直接返回
        155
    sine   303 天前 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   303 天前 via Android
    @celeron533 厉害
        157
    xavierskip   303 天前
    啊,按照直观想法一个一个数字生成呗。每一个会收到前一个随机数字的影响,哪怕最后一个数字不用选,怎么就不随机了呢?
        158
    Linxing   303 天前
    @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   303 天前
    代码乱了 反正每次都要判断一下余下的值是否够不够随机
    @bloodspasm 判断下余下的数啊
        160
    akira   303 天前
    用 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   303 天前
        162
    jiangbingo   303 天前
    @mayorbryant 这个算法算是工程化的 code,可以跑 UT 了。
        163
    mayorbryant   303 天前
    @jiangbingo 可以再优化一下,比如说 py2 的 xrange 和 range, 当人数和金额比较大时,range 会有性能问题,换成 xrange 会快很多,都是秒出
        164
    DiamondbacK   302 天前 via Android
    随机变量应当服从什么分布,本来是问题本身应该给出的条件。问题给不出,回答就肯定一团乱,或者在一个不明确的分布下给出算法,或者变成争论哪种分布才合理(只要问题限定了分布,就仅仅是一个计算条件,不存在合理不合理)。
        165
    woshigehaoren   301 天前
    随机 10 个数,然后把他们加起来,然后按百分比分。
        166
    1150774022   279 天前
    @benmo 随机出来的每个数字乘 3 不就解决了?
        167
    Slyutae   258 天前 via Android
    随机前 9 个数,如果前 9 个数的 sum 大于 100,丢掉,如果小于 100,100-sum 就是第 10 个数
    1  2  
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4312 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 31ms · UTC 02:15 · PVG 10:15 · LAX 18:15 · JFK 21:15
    ♥ Do have faith in what you're doing.