Python 生成具有已知总数的随机整数数组

Python 生成具有已知总数的随机整数数组,python,python-3.x,numpy,Python,Python 3.x,Numpy,考虑以下玩具数组,其整数范围为5-25: a = np.array([12, 18, 21]) 如何生成从1-5范围内的5随机整数数组 它等于数组中的每个数字a?解决方案应在所有可能的输出上产生均匀分布 到目前为止,我已经创建了一个简单的函数,它将生成5random 整数: import numpy as np def foo(a, b): p = np.ones(b) / b return np.random.multinomial(a, p, size = 1) 使用数

考虑以下玩具数组,其整数范围为
5
-
25

a = np.array([12, 18, 21])
如何生成从
1
-
5
范围内的
5
随机整数数组 它等于数组中的每个数字
a
?解决方案应在所有可能的输出上产生均匀分布

到目前为止,我已经创建了一个简单的函数,它将生成
5
random 整数:

import numpy as np

def foo(a, b):
    p = np.ones(b) / b
    return np.random.multinomial(a, p, size = 1)
使用数组
a
*中的值的示例:

In [1]: foo(12, 5)
Out[1]: array([[1, 4, 3, 2, 2]])

In [2]: foo(18, 5)
Out[2]: array([[2, 2, 3, 3, 8]])

In [3]: foo(21, 5)
Out[3]: array([[6, 5, 3, 4, 3]])
显然,这些整数具有所需的总数,但它们并不总是有界的 介于
1
5
之间

预期产出将大致如下:

In [4]: foo(np.array([12, 18, 21]), 5)
Out[4]: 
array([[1, 4, 3, 2, 2],
       [4, 3, 3, 3, 5],
       [5, 5, 4, 4, 3]])


*
np.multinomial()
)函数只将整数作为参数。

这里是一个快速而肮脏的版本(不容易矢量化,也不保证在可能的输出上有任何特定的分布):

它一次选择一个随机整数

在每一点上,它都知道需要(随机)将
总数的多少分配给剩余的多少个数字(
num
)。它使用这些知识来调整
边界,使其始终保持在约束范围内

此方法会使以后选择的数字偏向于具有更大的值。
shuffle
消除了这种偏差(但是,同样地,对于可能输出的总体分布不提供任何保证)


我很高兴知道是否有更好的方法来实现这一点。

修改了代码以支持阵列

>>> def foo(a, b):
    p = np.ones(b) / b
    arr = []
    if isinstance(a, type(np.array([]))):
        for i in a:
            arr.extend(np.random.multinomial(i, p, size = 1))
        return np.array(arr)
    return np.random.multinomial(a, p, size = 1)

输出:

>>> foo(np.array([12, 18, 21]), 5)
array([[3, 4, 3, 1, 1],
       [5, 4, 3, 4, 2],
       [3, 5, 3, 6, 4]])

你可以像下面这样使用多项式,其中
np.random。多项式(s,[1/5]*5)
类似于画一个有5个边的无偏骰子s次,它返回每边在每次绘制中出现的次数。所以,每次出现的计数总和必须等于总抽签数

def foo(arr,n):
返回np.r_[[arr中s的np.random.多项式(s[1/5]*5)]]
foo(np.array([12,18,21]),5)

以下与我的另一个答案不同之处在于,它保证了所有可能输出的均匀分布,但可能仅适用于小输入(可能输出的空间相当小):

这将在选择一个输出之前将所有可能的输出具体化为一个列表。在实践中,可能会使用(1号)


如果您需要生成具有相同参数的多个随机集,则替换储层采样可能是答案:

这里的解决方案可能比其他解决方案更脏。但只要
随机,它就会给出均匀分布

from random import sample

def foo(num, count):
    result = [1] * count
    indices = set(range(count))

    for _ in range(num - count):
        idx = sample(indices, 1)[0]
        result[idx] += 1

        if result[idx] == 5:
            indices.remove(idx)

    return result

print(foo(16, 4))
# [4, 4, 5, 3]
它从一个
的列表开始,不断添加
+1
,直到达到目标,还跟踪哪个值达到了5,因此不再在那里添加。相当基本和缓慢的解决方案。(仍然
O(n)

编辑:我为一个值编写了它,您必须在循环中应用它以获得多个结果。

这里是一个精确的(每个法定金额都有相同的概率)解决方案。它使用所有合法和的枚举,并不是说我们遍历每一个和,而是给定一个数字n,我们可以直接计算枚举中的第n个和。由于我们也知道合法和的总数,我们可以简单地绘制统一整数并进行转换:

import numpy as np
import functools as ft

#partition count
@ft.lru_cache(None)
def capped_pc(N,k,m):
    if N < 0:
        return 0
    if k == 0:
        return int(N<=m)
    return sum(capped_pc(N-i,k-1,m) for i in range(m+1))

capped_pc_v = np.vectorize(capped_pc)

def random_capped_partition(low,high,n,total,size=1):
    total = total - n*low
    high = high - low
    if total > n*high or total < 0:
        raise ValueError
    idx = np.random.randint(0,capped_pc(total,n-1,high),size)
    total = np.broadcast_to(total,(size,1))
    out = np.empty((size,n),int)
    for j in range(n-1):
        freqs = capped_pc_v(total-np.arange(high+1),n-2-j,high)
        freqs_ps = np.c_[np.zeros(size,int),freqs.cumsum(axis=1)]
        out[:,j] = [f.searchsorted(i,"right") for f,i in zip(freqs_ps[:,1:],idx)]
        idx = idx - np.take_along_axis(freqs_ps,out[:,j,None],1).ravel()
        total = total - out[:,j,None]
    out[:,-1] = total.ravel()
    return out + low
将numpy导入为np
将工具作为ft导入
#分区计数
@ft.lru_缓存(无)
def封盖_pc(N、k、m):
如果N<0:
返回0
如果k==0:
返回整数(N*N高或总计<0:
升值误差
idx=np.random.randint(0,上限为pc(总计,n-1,高),大小)
总计=np.广播至(总计(尺寸1))
out=np.空((大小,n),int)
对于范围(n-1)内的j:
频率=上限值(总np.arange(高+1),n-2-j,高)
freqs\u ps=np.c\n[np.zeros(size,int),freqs.cumsum(axis=1)]
out[:,j]=[f.searchsorted(i,“right”)表示f,i在zip中(freqs_ps[:,1:,idx)]
idx=idx-np.沿_轴取_(freqs_ps,out[:,j,None],1).ravel()
总计=总计输出[:,j,无]
out[:,-1]=total.ravel()
返回+低
演示:

#1到5之间的4个值相加为12
#一百万个样本需要几秒钟
x=随机分区(1,5,4,121000000)
#健康检查
#值介于1和5之间
x、 最小值(),x.最大值()
# (1, 5)
#所有法定金额都会发生
#把他们算作蛮力吧
范围(1,6)内的a的和(1),范围(1,6)内的b的和(1,6)内的c的和,如果7
from random import sample

def foo(num, count):
    result = [1] * count
    indices = set(range(count))

    for _ in range(num - count):
        idx = sample(indices, 1)[0]
        result[idx] += 1

        if result[idx] == 5:
            indices.remove(idx)

    return result

print(foo(16, 4))
# [4, 4, 5, 3]
import numpy as np
import functools as ft

#partition count
@ft.lru_cache(None)
def capped_pc(N,k,m):
    if N < 0:
        return 0
    if k == 0:
        return int(N<=m)
    return sum(capped_pc(N-i,k-1,m) for i in range(m+1))

capped_pc_v = np.vectorize(capped_pc)

def random_capped_partition(low,high,n,total,size=1):
    total = total - n*low
    high = high - low
    if total > n*high or total < 0:
        raise ValueError
    idx = np.random.randint(0,capped_pc(total,n-1,high),size)
    total = np.broadcast_to(total,(size,1))
    out = np.empty((size,n),int)
    for j in range(n-1):
        freqs = capped_pc_v(total-np.arange(high+1),n-2-j,high)
        freqs_ps = np.c_[np.zeros(size,int),freqs.cumsum(axis=1)]
        out[:,j] = [f.searchsorted(i,"right") for f,i in zip(freqs_ps[:,1:],idx)]
        idx = idx - np.take_along_axis(freqs_ps,out[:,j,None],1).ravel()
        total = total - out[:,j,None]
    out[:,-1] = total.ravel()
    return out + low
# 4 values between 1 and 5 summing to 12
# a million samples takes a few seconds
x = random_capped_partition(1,5,4,12,1000000)

# sanity checks

# values between 1 and 5
x.min(),x.max()
# (1, 5)

# all legal sums occur
# count them brute force
sum(1 for a in range(1,6) for b in range(1,6) for c in range(1,6) if 7 <=     a+b+c <= 11)
# 85
# and count unique samples
len(np.unique(x,axis=0))
# 85

# check for uniformity
np.unique(x, axis=0, return_counts=True)[1]
# array([11884, 11858, 11659, 11544, 11776, 11625, 11813, 11784, 11733,
#        11699, 11820, 11802, 11844, 11807, 11928, 11641, 11701, 12084,
#        11691, 11793, 11857, 11608, 11895, 11839, 11708, 11785, 11764,
#        11736, 11670, 11804, 11821, 11818, 11798, 11587, 11837, 11759,
#        11707, 11759, 11761, 11755, 11663, 11747, 11729, 11758, 11699,
#        11672, 11630, 11789, 11646, 11850, 11670, 11607, 11860, 11772,
#        11716, 11995, 11802, 11865, 11855, 11622, 11679, 11757, 11831,
#        11737, 11629, 11714, 11874, 11793, 11907, 11887, 11568, 11741,
#        11932, 11639, 11716, 12070, 11746, 11787, 11672, 11643, 11798,
#        11709, 11866, 11851, 11753])