I couldn't find a fast numpy
solution without a massive (n x size x int32) space overhead. So here is a numba
implementation of @RichardYannow's solution. Please remember to exclude the first call from benchmarks.
The sampling algorithm is a simplified adaptation of np.rng.choice
, size=1 samples should be "np.squeezed
" to remove the unused dimension.
import numpy as np
import numba as nb # tested with numba 0.55.1
@nb.njit
def allocate(n, k, size):
idx = np.zeros((size, k+1), np.int32)
s = np.empty(n+k, np.uint8)
for j in range(size):
s.fill(0)
for i in range(n+1, n + k):
x = np.random.randint(i) + 1
if s[x]: x = i
s[x] = 1
idx[j, i - n] = x
idx[j, 1:k].sort()
idx[j, k] = n+k
for i in range(k):
idx[j, i] = idx[j, i+1] - idx[j, i] - 1
return idx[:, :k]
allocate(4, 3, size=5)
Output
array([[1, 1, 2],
[0, 3, 1],
[1, 1, 2],
[1, 1, 2],
[1, 2, 1]], dtype=int32)
Checking uniform distribution across all possible allocations
from collections import Counter
Counter(tuple(x) for x in allocate(4, 3, size=10000))
Output
Counter({(0, 0, 4): 685,
(0, 1, 3): 680,
(0, 2, 2): 646,
(0, 3, 1): 666,
(0, 4, 0): 662,
(1, 0, 3): 660,
(1, 1, 2): 670,
(1, 2, 1): 661,
(1, 3, 0): 647,
(2, 0, 2): 650,
(2, 1, 1): 699,
(2, 2, 0): 682,
(3, 0, 1): 667,
(3, 1, 0): 669,
(4, 0, 0): 656})