创建受约束的随机数?

时间:2013-08-26 16:12:46

标签: python algorithm statistics probability

清理文字:

如何创建添加upp的m = 5个随机数,比如n = 100。但是,第一个随机数是10,&lt; x1&lt;在图30中,第二随机nr是5 < x2&lt;在图20中,第三随机nr是10 < x3&lt;因此,这五个随机数加起来为100.我如何创建这些受约束的五个数字?

[[

相关问题A1):创建五个随机数加起来为100的标准方法是在[0,100]之间采样四个数字,并添加边界0和100,然后对这六个数字进行排序[0,x1 ,X2,X3,x4,100]。我寻求的五个随机数是增量。也就是说,

100 - x[4] = delta 5
x[4]- x[3] = delta 4
x[3]- x[2] = delta 3
x[2]- x[1] = delta 2
x[1] - 0   = delta 1

这五个增量现在加起来为100.例如,它们可能是0,1,2,7,90。以下是一些解决此问题的代码:

total_sum = 100
n = 5
v = numpy.random.multinomial(total_sum, numpy.ones(n)/n)

]]

对于我的问题,我不能允许宽间隔发生,上面的最大扩展是90-7 = 83这太宽了。所以,我必须指定更严格的传播,比如[10,30]。这意味着最大的随机数是30,这不允许大的点差,如83.

[[

相关问题A2):用相同的边界创建五个数字的部分解决方案,10&lt; x_i&lt; 30,最多100就是这样:只是在A1中做,但是将下边界10添加到增量。所以我得到了我想要的五个随机数:

100 - x[4] = delta 5 + 10
x[4]- x[3] = delta 4 + 10
x[3]- x[2] = delta 3 + 10
x[2]- x[1] = delta 2 + 10
x[1] - 0   = delta 1 + 10

基本上,我确实喜欢A1),但不是从0开始,而是从10开始。因此,每个数字都有下边界10,但它们没有上边界,它可能很大,太大。如何将上限限制在30?这里的问题是如何限制上边界

]]

总结一下,我试图解决的问题类型如下:我需要五个随机数加起来为100,我需要为每个数字单独指定边界,比如说[10,30]为第一个随机数数字,然后[5,10]为第二个随机数,[15,35]为第三个随机数,等等。并且它们必须加起来为100.

但是我使用的真实数据有~100个数字x_i(m = 50),所有这些数据总计大约400,000。对于数字x_i,范围通常为[3000,5000]。这些数字并不准确,我只想传达一些关于问题规模的信息。目的是进行MCMC模拟,以便快速生成这些数字。人们已经提出了非常优雅的解决方案,它们确实有效,但是它们需要很长时间,因此我无法使用它们。问题仍未解决。理想情况下,我想要一个O(m)解决方案和O(1)内存解决方案。

这个问题不应该是NP难的,它不会有这种感觉。应该有一个多项式时间解决方案,对吗?

5 个答案:

答案 0 :(得分:4)

假设您需要[10,30]中的n_1,[20,40]中的n_2,[30,50]中的n_3和n1 + n2 + n3 = 90

如果你需要每个可能的三元组(n_1,n_2,n_3)同样可能,那将会很困难。形式(20,n_2,n_3)的三元组数量大于三元组数(10,n_2,n_3),因此您不能统一选择n_1。

令人难以置信的缓慢但准确的方法是在正确的范围内生成所有5个random,如果总和不正确则拒绝整个组。

。 。 。 AHA!

我找到了一种有效地参数化选择的方法。首先,为简单起见,请注意,下限之和是最小可能总和。如果从目标数中减去下限之和并从每个生成的数中减去下限,则会出现问题,其中每个数字都在区间[0,max_k-min_k]中。这简化了数学和数组(列表)处理。设n_k为基于0的选择,0 <= n_k&lt; = max_k-min_k。

总和的顺序是词典,所有总和首先以n_1 = 0(如果有)开头,然后是n_1 == 1总和,等等。总和按每个组中的n_2排序,然后按n_3排序,等等。如果您知道有多少总和添加到目标(称为T),以及有多少总和以n_1 = 0,1,2,...开头,那么您可以在该列表中找到总数S in的起始编号n1。然后你可以减少添加n_2 + n_3 + ...以获得T-n_1的问题,找到总和S - (数字小于n_1的原始总数)。

令pulse(n)为n + 1个列表:(n + 1)* [1]用Python术语表示。设max_k,min_k为第k个选择的限制,m_k = max_k-min_k为基于0的选择的上限。然后,从第一个数字的选择中得到1 + m_1个不同的“和”,脉冲(m_k)给出分布:1是使每个和从0到m_1。对于前两个选项,有m_1 + m_ + 1个不同的总和。事实证明,脉冲(m_1)与脉冲(m_2)的卷积给出了分布。

停止使用某些代码的时间:

    def pulse(width, value=1):
        ''' Returns a vector of (width+1) integer ones. '''
        return (width+1)*[value]

    def stepconv(vector, width):
        ''' Computes the discrete convolution of vector with a "unit"
            pulse of given width.

            Formula: result[i] = Sum[j=0 to width] 1*vector[i-j]
            Where 0 <= i <= len(vector)+width-1, and the "1*" is the value
            of the implied unit pulse function: pulse[j] = 1 for 0<=j<=width.
        '''
        result = width*[0] + vector;
        for i in range(len(vector)):
            result[i] = sum(result[i:i+width+1])
        for i in range(len(vector), len(result)):
            result[i] = sum(result[i:])
        return result

这是专门为仅使用“脉冲”数组进行卷积编码的,因此卷积中的每个线性组合只是一个总和。

仅在最终类解决方案的构造函数中使用它们:

class ConstrainedRandom(object):
    def __init__(self, ranges=None, target=None, seed=None):
        self._rand = random.Random(seed)
        if ranges != None: self.setrange(ranges)
        if target != None: self.settarget(target)

    def setrange(self, ranges):
        self._ranges = ranges
        self._nranges = len(self._ranges)
        self._nmin, self._nmax = zip(*self._ranges)
        self._minsum = sum(self._nmin)
        self._maxsum = sum(self._nmax)
        self._zmax = [y-x for x,y in self._ranges]
        self._rconv = self._nranges * [None]
        self._rconv[-1] = pulse(self._zmax[-1])
        for k in range(self._nranges-1, 0, -1):
            self._rconv[k-1] = stepconv(self._rconv[k], self._zmax[k-1])

    def settarget(self, target):
        self._target = target

    def next(self, target=None):
        k = target if target != None else self._target
        k = k - self._minsum;
        N = self._rconv[0][k]
        seq = self._rand.randint(0,N-1)
        result = self._nranges*[0]
        for i in range(len(result)-1):
            cv = self._rconv[i+1]
            r_i = 0
            while k >= len(cv):
                r_i += 1
                k -= 1
            while cv[k] <= seq:
                seq -= cv[k]
                r_i += 1
                k -= 1
            result[i] = r_i
        result[-1] = k # t
        return [x+y for x,y in zip(result, self._nmin)]

    # end clss ConstrainedRandom

用于:

ranges = [(low, high), (low, high), ...]
cr = ConstrainedRandom(ranges, target)
seq = cr.next();
print(seq)
assert sum(seq)==target

seq = cr.next(); # get then get the next one.

...等。该类可以减少一点,但主要的空间开销是在_rconv列表中,它具有存储的卷积。对于O(NT)存储,这大致是N * T / 2。

卷积仅使用范围,在相同约束下生成大量随机数,表格构建时间“摊销”为零。根据_rconv列表的索引数,.next()的时间复杂度平均为大约T / 2,O(T)。


要了解算法的工作原理,假设一系列基于零的选择,最大值(5,7,3)和基于0的目标T = 10。在空闲会话中定义或导入pulse和stepconv函数,然后:

>>> pulse(5)
[1, 1, 1, 1, 1, 1]
>>> K1 = pulse (5)
>>> K2 = stepconv(K1, 7)
>>> K3 = stepconv(K2, 3)
>>> K1
[1, 1, 1, 1, 1, 1]
>>> K2
[1, 2, 3, 4, 5, 6, 6, 6, 5, 4, 3, 2, 1]
>>> K3
[1, 3, 6, 10, 14, 18, 21, 23, 23, 21, 18, 14, 10, 6, 3, 1]
>>> K3[10]
18
>>> sum(K3)
192
>>> (5+1)*(7+1)*(3+1)
192

K3 [i]示出了不同选择n_1,n_2,n_3的数量,使得0 <= n_k <= m_k且Σn_k= i。当应用于其中两个列表时,让*表示卷积。然后脉冲(m_2)*脉冲(m_3)给出n_2和n_3之和的分布:

>>> R23 = stepconv(pulse(7),3)
>>> R23
[1, 2, 3, 4, 4, 4, 4, 4, 3, 2, 1]
>>> len(R23)
11

从0到T = 10的每个值都是(几乎不可能),因此对于第一个数字可以进行任何选择,并且存在R23 [T-n_1]可能的三元组,其添加到以N1开始的T = 10。所以,一旦你发现有18个可能的和加到10,产生一个随机数S = randint(18)并通过R23 [T:T-m_1-1:-1]数组倒计数:

>>> R23[10:10-5-1:-1]
[1, 2, 3, 4, 4, 4]
>>> sum(R23[10:10-5-1:-1])
18

注意该列表的总和是在上面的K3 [10]中计算的总和。理智检查。无论如何,如果S == 9是随机选择,那么找出该数组的多少个主要项可以求和而不超过S.这就是n_1的值。在这种情况下,1 + 2 + 3 <= S但是1 + 2 + 3 + 4> 1。 S,所以n_1是3.

如上所述,您可以减少问题以找到n_2。最终的数字(本例中为n_3)将是唯一确定的。

答案 1 :(得分:3)

首先,定义您的范围:

ranges = [range(11,30), range(6,20), range(11,25)]

然后列举所有可能性:

def constrainedRandoms(ranges):
    answer = []
    for vector in itertools.product(*ranges):
        if sum(ranges) == 100:
            answer.append(vector)
    return answer

等效单行:

answer = [v for v in itertools.product(*ranges) if sum(v)==100]

然后从中挑选一个随机元素:

myChoice = random.choice(answer)

编辑:

笛卡尔积(用itertools.product计算)本身并不会占用太多RAM(这是因为itertools.product返回一个使用O(1)空间的生成器,但很多时候就像你一样指出)。仅计算列表(answer)。坏消息是,为了使用random.choice,您需要一个列表。如果你真的不想使用列表,那么你可能需要提出一个衰减概率函数。这是一个非常简单的概率函数:

def constrainedRandoms(ranges):
    choices = (v for v in itertools.product(*ranges) if sum(v)==100) # note the parentheses. This is now a generator as well

    prob = 0.5
    decayFactor = 0.97 # set this parameter yourself, to better suit your needs
    try:
        answer = next(choices)
    except StopIteration:
        answer = None
    for choice in choices:
        answer = choice
        if random.uniform(0,1) >= prob:
            return answer
        prob *= decayFactor
    return answer

衰减概率允许您增加选择适合约束的下一个向量的概率。以这种方式思考:你有一堆约束。有可能你将拥有相对较少数量的满足这些约束的向量。这意味着每次您决定不使用向量时,存在另一个此类向量的概率会降低。因此,随着时间的推移,您需要逐渐更偏向于将当前向量作为“随机选择的向量”返回。当然,仍然可以在不返回向量的情况下遍历整个循环结构。这就是为什么代码以适合约束的第一个向量的默认值开始(假设一个存在)并且如果衰减概率永远不允许返回向量,则返回最后一个这样的向量。 请注意,这种衰减概率思想允许您不必遍历所有候选向量。随着时间的推移,代码返回所考虑的当前向量的概率增加,从而降低了它继续并考虑其他向量的概率。因此,这个想法也有助于缓解您对运行时的担忧(但是,我很尴尬地添加,而不是非常多)

答案 2 :(得分:2)

试试这个:

import random
a = random.randint(10, 30)
b = random.randint(5, 20)
c = random.randint(10, 25)
d = random.randint(5, 15)
e = 100 - (a+b+c+d)

修改

以下是给定n范围约束和所需目标总和的n随机数列表的方法:

def generate(constraints, limit):
    ans = [random.choice(c) for c in constraints]
    return ans if sum(ans) == limit else generate(constraints, limit)

这就是你如何使用它:

generate([range(10,31),range(5,21),range(10,26),range(5,16),range(10,26)], 100)
=> [25, 20, 25, 12, 18]

但请注意:如果约束不能保证最终达到总和,则会出现无限循环和堆栈溢出错误,例如:

generate([range(1,11), range(10, 21)], 100)
=> RuntimeError: maximum recursion depth exceeded while calling a Python object

答案 3 :(得分:0)

这是一个通用的解决方案:

import random
def constrained_rndms(constraints, total):
    result = []
    for x, y in constraints:
        result.append(random.randint(x,y))
    result.append(total - sum(result))
    return result

s = constrained_rndms([(10,20),(5,20),(10,25),(5,15)],100) # -- [19, 5, 16, 15, 45]
sum(s) # -- 100

答案 4 :(得分:0)

可以使用两个跨度,四个跨度,八个跨度等来计算每个可能总数的方法的数量(其中跨度是包括其端点的整数范围)。将这些数字制成表格后,您可以向后查找样本。例如,假设有16个跨度,每个跨度包括数字1到9.一个发现有w = 202752772954792种方法可以总计t = 100。选择1到w范围内的随机数r。搜索(或查找)以找到一个数字J,使得leftways(t-j)*rightways(j))的J> j的总和超过r,而J> j + 1的总和不会,其中leftways(i)是方法的数量让我使用前8个跨度,rightways(j)是使用最后8个跨度制作j的方法的数量。使用i = t-j进行前8个跨度和j进行递归8等等。只要有一种方法可以获得所需的总数,就会出现基本情况。

通过按宽度按降序排序(即,首先列出最宽的跨度)并删除一些交换,可以修改下面的代码以更有效地运行。请注意,如果跨度不按宽度降序排列,则结果向量的顺序与跨距的顺序不同。此外,有可能通过二分搜索替换for j ...中的线性findTarget搜索,但我还没有想出如何在不使用更多空间的情况下这样做。

通过使用对象存储树结构而不仅仅是元组,可以使代码更清晰,更清晰。

如果有m个跨度,其最大值总计为s,则使用的空间可能为O(s²·(lg m))。所用时间为O(s²·(lg m)),用于产品总和的初始制表,每个样本可能O(m+(lg m)·(s/m))O(m+(lg m)·s)。 (我没有正确分析空间和时间要求。)在2GHz机器上,如果有16个相同的跨度1 ... 10,则所示代码每秒产生大约8000个样本;如果有32个相同的跨度1 ... 3,则每秒约5000个样本;如果有32个相同的跨度1 ... 30,则每秒约2000个样本。代码后显示了各种目标总数和跨度集的一些示例输出。

from random import randrange
def randx(hi):   # Return a random integer from 1 to hi
    return 1+randrange(hi) if hi>0 else 0

# Compute and return c with each cell k set equal to 
# sum of products a[k-j] * b[j], taken over all relevant j
def sumprods(lt, rt):
    a, b = lt[0], rt[0]
    (la,ma,aa), (lb,mb,bb) = a, b
    if ma-la < mb-lb:           # Swap so |A| >= |B|
        la, ma, aa, lb, mb, bb = lb, mb, bb, la, ma, aa
    lc, mc = la+lb, ma+mb
    counts = [0]*(mc+1)
    for k in range(lc,mc+1):
        for j in range(max(lb,k-ma), min(mb,k-la)+1):
            counts[k] += aa[k-j] * bb[j]
    return (lc, mc, counts)

def maketree(v):
    lv = len(v)
    if lv<2:
        return (v[0], None, None)
    ltree = maketree(v[:lv/2])
    rtree = maketree(v[lv/2:])
    return (sumprods(ltree, rtree), ltree, rtree)


def findTarget(tototal, target, tree):
    global result
    lt, rt = tree[1], tree[2]
    # Put smaller-range tree second
    if lt[0][1]-lt[0][0] < rt[0][1]-rt[0][0]: lt, rt = rt, lt
    (la,ma,aa), (lb,mb,bb) = lt[0], rt[0]
    lc, mc = la+lb, ma+mb
    count = 0
    for j in range(max(lb,tototal-ma), min(mb,tototal-la)+1):
        i = tototal-j
        count += aa[i] * bb[j]
        if count >= target: break
    # Suppose that any way of getting i in left tree is ok
    if lt[1]: findTarget(i, randx(aa[i]), lt)
    else: result += [i]
    # Suppose that any way of getting j in right tree is ok
    if rt[1]: findTarget(j, randx(bb[j]), rt)
    else: result += [j]

spans, ttotal, tries = [(1,6), (5,11), (2,9), (3,7), (4,9), (4,12), (5,8),
                        (3,5), (2,9), (3,11), (3,9), (4,5), (5,9), (6,13),
                        (7,8), (4,11)],  100, 10

spans, ttotal, tries = [(10*i,10*i+9) for i in range(16)], 1300, 10000

spans, ttotal, tries = [(1,3) for i in range(32)],  64, 10000

spans, ttotal, tries = [(1,10) for i in range(16)], 100, 10

print 'spans=', spans
vals = [(p, q, [int(i>=p) for i in range(q+1)]) for (p,q) in spans]
tree = maketree(vals)
nways = tree[0][2][ttotal]
print 'nways({}) = {}'.format(ttotal, nways)
for i in range(1,tries):
    result = []
    findTarget(ttotal, randx(nways), tree)
    print '{:2}: {}  {}'.format(i, sum(result), result)

在下面显示的输出样本中,带冒号的行包含样本编号,样本总数和样本值数组。其他线条显示了一组跨度以及制作所需总数的方式。

spans= [(1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10), (1, 10)]
nways(100) = 202752772954792
 1: 100  [8, 9, 1, 2, 8, 1, 10, 6, 6, 3, 6, 10, 6, 10, 10, 4]
 2: 100  [2, 6, 10, 3, 1, 10, 9, 5, 10, 6, 2, 10, 9, 7, 4, 6]
 3: 100  [1, 6, 5, 1, 9, 10, 10, 7, 10, 2, 8, 9, 10, 9, 2, 1]
 4: 100  [10, 7, 6, 3, 7, 8, 6, 5, 7, 7, 5, 1, 9, 6, 9, 4]
 5: 100  [7, 1, 10, 5, 5, 4, 9, 5, 3, 9, 2, 8, 6, 8, 10, 8]
    spans= [(1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3), (1, 3)]
nways(64) = 159114492071763
 1: 64  [2, 2, 1, 3, 1, 2, 2, 2, 1, 2, 3, 3, 3, 2, 2, 1, 2, 3, 1, 3, 1, 3, 2, 1, 2, 3, 2, 2, 1, 2, 2, 2]
 2: 64  [1, 2, 1, 1, 1, 3, 3, 3, 2, 1, 1, 2, 3, 2, 2, 3, 3, 3, 1, 2, 1, 2, 2, 3, 2, 2, 1, 3, 1, 3, 2, 2]
 3: 64  [2, 1, 3, 2, 3, 3, 1, 3, 1, 3, 2, 2, 1, 2, 1, 3, 1, 3, 1, 2, 2, 2, 2, 1, 1, 3, 2, 2, 3, 2, 3, 1]
 4: 64  [2, 3, 3, 2, 3, 2, 1, 3, 2, 2, 1, 2, 1, 1, 3, 2, 2, 3, 3, 1, 1, 2, 2, 1, 1, 2, 3, 3, 2, 1, 1, 3]
 5: 64  [1, 1, 1, 3, 2, 2, 3, 2, 2, 1, 2, 2, 1, 2, 1, 1, 3, 3, 2, 3, 1, 2, 2, 3, 3, 3, 2, 2, 1, 3, 3, 1]
spans= [(0, 9), (10, 19), (20, 29), (30, 39), (40, 49), (50, 59), (60, 69), (70, 79), (80, 89), (90, 99), (100, 109), (110, 119), (120, 129), (130, 139), (140, 149), (150, 159)]
nways(1323) = 5444285920
 1: 1323  [8, 19, 25, 35, 49, 59, 69, 76, 85, 99, 108, 119, 129, 139, 148, 156]
 2: 1323  [8, 16, 29, 39, 48, 59, 69, 77, 88, 95, 109, 119, 129, 138, 147, 153]
 3: 1323  [9, 16, 28, 39, 49, 58, 69, 79, 87, 96, 106, 115, 128, 138, 149, 157]
 4: 1323  [8, 17, 29, 36, 45, 58, 69, 78, 89, 99, 106, 119, 128, 135, 149, 158]
 5: 1323  [9, 16, 27, 34, 48, 57, 69, 79, 88, 99, 109, 119, 128, 139, 144, 158]
    spans= [(1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (
1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (
1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (1, 30), (
1, 30), (1, 30), (1, 30)]
nways(640) = 19144856039395888221416547336829636235610525
 1: 640  [7, 24, 27, 9, 30, 23, 30, 27, 28, 29, 2, 30, 28, 19, 7, 27, 10, 2, 21, 23, 24, 2
7, 24, 16, 29, 8, 13, 23, 2, 19, 27, 25]
 2: 640  [30, 2, 17, 28, 30, 16, 5, 1, 26, 24, 22, 19, 26, 16, 16, 30, 27, 15, 19, 30, 15,
 30, 22, 5, 30, 9, 13, 25, 19, 15, 30, 28]
 3: 640  [2, 24, 1, 23, 20, 5, 30, 22, 24, 19, 22, 9, 28, 29, 5, 24, 14, 30, 24, 16, 26, 2
1, 26, 20, 20, 19, 24, 29, 24, 8, 23, 29]
 4: 640  [7, 20, 16, 24, 22, 14, 28, 28, 26, 8, 21, 9, 22, 24, 28, 19, 5, 13, 9, 24, 25, 2
2, 29, 18, 20, 21, 17, 26, 30, 9, 26, 30]