【问题标题】:"Slice" a number into three random numbers将一个数字“切片”为三个随机数
【发布时间】:2014-12-18 13:43:26
【问题描述】:

我需要生成一个文件,其中每行(10 行)填充三个“随机”值,但这些值的总和必须等于 15。

结构是:“INDEX A B C”。

例子:

1 15 0 0
2 0 15 0
3 0 0 15
4 1 14 0
5 2 13 0
6 3 12 0
7 4 11 0
8 5 10 0
9 6 9 0
10 7 8 0

【问题讨论】:

  • “随机”在这里你真的需要任何真正的熵吗(你的前三个例子似乎认为你不需要,但这可能会产生误导),或者你只是说“任意的,我不在乎”?
  • 这正好解决了我的问题。谢谢!

标签: python random numbers slice


【解决方案1】:

如果您想避免创建(或迭代)满足排列的完整空间(这对于大型 N 很重要),那么您可以使用顺序样本解决此问题.

我的第一种方法是从 [0, N] 中统一绘制一个值,称之为x。然后从[0, N-x]中统一画一个值,取名为y,然后设置z = N - x - y。如果你然后将这三个洗牌,你会从解决方案的空间中得到一个合理的平局,但它不会完全一致。

例如,考虑N=3 的位置。那么 (3, 0, 0) 的某些排列的概率是 1/4,即使它只是 10 个可能的三元组中的一个。所以这个特权值包含一个很高的最大值。

您可以通过根据xy 可能的值的数量按比例对第一个值x 进行采样,从而完全抵消这种影响。例如,如果x恰好是N,那么y只有1个兼容值,但如果x为0,则有4个兼容值,即0到3。

换句话说,对于从 0 到 Ni,让 Pr(X=x) 成为 (N-x+1)/sum_i(N-i+1)。然后让Pr(Y=y | X=x) 在 [0, N-x] 上一致。

结果为 P(X,Y) = P(Y|X=x) * P(X) = 1/(N-x+1) * [N - x + 1]/sum_i(N- i+1),对于每个候选三元组,它看起来是一致的,1/sum_i(N-i+1)。

请注意,sum(N-i+1 for i in range(0, N+1)) 给出了将 3 个非负整数相加得到 N 的不同方法的数量。我不知道一个很好的证明,如果有人在 cmets 中添加一个,我会很高兴!

这里有一个以这种方式采样的解决方案:

import random
from collections import Counter

def discrete_sample(weights):
    u = random.uniform(0, 1)
    w_t = 0
    for i, w in enumerate(weights):
        w_t += w
        if u <= w_t:
            return i
    return len(weights)-1

def get_weights(N):
    vals = [(N-i+1.0) for i in range(0, N+1)]
    totl = sum(vals)
    return [v/totl for v in vals]

def draw_summing_triplet(N):
    weights = get_weights(N)
    x = discrete_sample(weights)
    y = random.randint(0, N-x)
    triplet = [x, y, N - x - y]
    random.shuffle(triplet)
    return tuple(triplet)

感谢 cmets 中的 @DSM 质疑我的原始答案并提供了良好的反馈。

在这种情况下,我们可以这样测试采样器:

foo = Counter(draw_summing_triplet(3) for i in range(10**6))
print foo

Counter({(1, 2, 0): 100381, 
         (0, 2, 1): 100250, 
         (1, 1, 1): 100027, 
         (2, 1, 0): 100011, 
         (0, 3, 0): 100002, 
         (3, 0, 0): 99977, 
         (2, 0, 1): 99972,
         (1, 0, 2): 99854, 
         (0, 0, 3): 99782, 
         (0, 1, 2): 99744})

【讨论】:

  • 我不确定我是否同意你关于统一性的论点。 x 上的分布是均匀的,但这意味着得到 (15, 0, 0) 有 1/16 的变化,也就是样本空间的 1/136。
  • 是的,我认为你是对的。您必须添加一个元分布,其中组件“x”将是(同样可能是元组中的点 1、2 或 3)。我缺少的属性是exchangeability,将修复。
  • 我相信可以修复它(在输出上做一个random.shuffle,这样每个组件都有平等的机会在值的范围内获得“第一个 dibs”。但请再次检查并发表评论。感谢您的检查!
  • 我认为这不起作用有两个原因。一种是肤浅的——random.shuffle 就地执行并返回None。 :-) 更深层次的是,虽然这处理了顺序问题,但它并不能解决 (15,0,0) 的 排列 仍然更有可能发生的事实。快速查看问题的一种方法是执行Counter(draw_summing_triplet(3) for i in range(10**6)) 之类的操作。
  • 你说的很对。我突然对这个问题更感兴趣了。
【解决方案2】:

如果数字可以任意组合使用:

from itertools import combinations
with open("rand.txt","w") as f:
    combs = [x for x in combinations(range(16),3) if sum(x ) == 15 ][:10]   
    for a,b,c in combs:
        f.write("{} {} {}\n".format(a,b,c))

【讨论】:

  • 我想你想要range(16),因为他的前三行。
  • 此外,丢弃您生成的 541/560 个组合有点低效……但我怀疑这是否重要,我认为这段代码的简单性足以弥补它。
  • @abarnert,是的,我改为 16。当然效率不高,但对于 OP 想要的应该没问题。
  • 另外,对于 OP:enumeratestart=1 将为您提供与 a, b, c 值一起使用的“索引”。 (我认为不需要在答案中;这不是他所问问题的有趣部分。)
  • 可行,但之前的解决方案似乎更适合我的需求。但是,谢谢!
【解决方案3】:

这对我来说似乎很简单,它利用了随机模块。

import random

def foo(x):
    a = random.randint(0,x)
    b = random.randint(0,x-a)
    c = x - (a +b)

    return (a,b,c)

for i in range(100):
    print foo(15)

【讨论】:

  • 这与我的答案相同,只是添加了不必要的 if 语句来检查 x-a == 0random.randint 可以为低和高取相同的值,例如 random.randint(0, 0)
  • 是的,你是对的,检查零并没有添加任何东西。
  • 我想如果random.randint 的实现对于random.randint(0, 0) 的情况非常低效,与进行if 比较的成本相比,这可能是值得的。但是random.randint 足够聪明,可以检查下限是否等于上限。
  • 我一定在考虑 range(0) 的情况下返回一个空数组。那好吧。我们得到了相同的基本答案,因为我正在编写答案并对其进行测试。在我的手机上看到它并考虑了一下。
  • 请注意,正如 DSM 在 cmets 中对我的回答所解释的那样,这不会在允许的三元组空间上创建统一样本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-28
  • 2018-04-22
  • 2021-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多