There are $d$ buckets, each one with capacity $[0,g]$. A quota of identical items $l\in \mathbb{N} $ is uniformly drawn from $[0,g\cdot d]$ and the items have to be distributed among buckets randomly. Buckets may be empty, or filled with at most $g$ items.
One trivial algorithm: Keep a set of all unfilled buckets $U$, then loop $l$ times each time uniformly sample from $U$ and place one item in that bucket, while removing the bucket from $U$ if it gets filled. This runs in $O(l \cdot log(d))$
Is there a way to do this more efficiently? In particular, I need $g$ to be able to scale to very large numbers. For example 5 buckets with capacity 1,000,000.
Note: It's important that $l$ is drawn uniformly, if we were to simply uniformly sample for each bucket how many items will be in each, then $l$ would be distributed according to the Irwin-Hall distribution, which we do not want. Otherwise that would have been a very efficient solution.
=============
Here is some more background on where this comes from:
A d-dimensional grid of grid size g+1. For example in 2-D, the grid goes from (0,0) to (g+1,g+1). Particles move from the origin (0,0) to the opposite corner. I need to sample on the position of a particle and it has to be unbiased. The length l of the path to the opposite side is g∗d and since particles are always moving one step at a time towards the goal, this distributes uniformly.
=============
Here is a python code for the solution given by Mike Earnest below:
import numpy as np
def distrib(l, g):
d = len(g)
a = [0] * d
if l <= 0:
return a
# Sampling phase
used = 0
for i in range(d):
a[i] = 0 if g[i] == 0 else np.random.binomial(n=l-used, p=1 / (d-i))
used += a[i]
# Siphon overflow into recursive call
used = 0
g_rem = []
for i in range(d):
a[i] = min(g[i], a[i])
used += a[i]
g_rem.append(g[i] - a[i])
l_rem = l - used
c = distrib(l_rem, g_rem)
# Combine
res = []
for i in range(len(g)):
res.append(a[i] + c[i])
return res
```
Here is a more efficient way to randomly distributed into the buckets. I believe this exactly produces the distribution that you are after. I am posting a new answer because this is very different from my other approach.
First, we fill one bucket at a time using an appropriate binomial distribution, while ignoring the upper limit of $g$. Then, after all buckets are filled, we siphon off the overfull buckets, and redistribute the overflow items into the remaining under-full buckets using the same process. To make the recursion work nicely, it helps to allow non-uniform capacities, instead of just a uniform $g$ for each bucket.
Steps $1, 2,$ and $4$ each take $O(d)$ steps. Each time we recurse, there is at least one more full bucket, so this process will loop at most $d$, resulting in $O(d^2)$ worst case performance. I suspect that the average case performance is closer to $O(d)$, but I cannot prove this.