This answer is a followup to the comments on my previous answer.
You said you want uniformly distributed numbers, but that of course is not possible to have while respecting the condition that the numbers must be spaced by at least 3 points.
So, I provide you a different definition of uniformly randomness: suppose you can enumerate all the valid vectors respecting your condition. I wrote a function to do so:
def space_gen(xmin, xmax, len, min_dist, result=[]):
if len:
for x in range(xmin, xmax - (len - 1) * min_dist):
yield from space_gen(x + min_dist, xmax, len - 1, min_dist, result + [x])
else:
yield result
Let's consider a smaller instance of the problem. Suppose you want vectors of 3 random numbers from 0 to 10 (excluded), spaced by at least 4 points:
>>> list(space_gen(0,10,3,4))
[[0, 4, 8], [0, 4, 9], [0, 5, 9], [1, 5, 9]]
that list is the complete enumeration of all valid results according to that rule.
Now you can uniformly sample from this list (see for example random.choice).
Now it's possible that your problem size (i.e. the range, or the vector size) make the problem instance too big to be exhaustively enumerated.
But you can still use this "brute force" enumeration to check if a method generates truly uniformly distributed samples.
For the problem instance of your question (0-20 range, 5 length, 3 min. dist) it's still doable:
>>> len(list(space_gen(0,21,5,3)))
1287
For example, we can check if rwp's recipe generates uniformly distributed samples (according to this definition):
space = list(space_gen(0, 21, 5, 3))
counter = {tuple(x): 0 for x in space}
for _ in range(200000):
x = tuple(map(int,generate()))
counter[x] += 1
import matplotlib.pyplot as plt
a = np.array(sorted(counter.values()))
plt.hist(a, bins=len(space))
plt.show()
and we observe this distribution of counts:

Clearly there are some vectors occurring way more often than other vectors.
We can also check the first solution I proposed:
def generate1():
maxn=15
while 1:
x = np.cumsum(np.ones((5,), np.int) * 3 + np.random.randint(0, maxn, (5,))) - 3
if x[-1] <= 20:
return x
even with maxn
=15 and using rejection sampling, it's still a bit skew and not perfectly uniform. Using the same benchmark/plot code as before:
