Reputation: 2476
Say I have a range(1, n + 1)
. I want to get m
unique pairs.
What I found is, if the number of pairs is close to n(n-1)/2
(maxiumum number of pairs), one can't simply generate random pairs everytime because they will start overriding eachother. I'm looking for a somewhat lazy solution, that will be very efficient (in Python's world).
My attempt so far:
def get_input(n, m):
res = str(n) + "\n" + str(m) + "\n"
buffet = range(1, n + 1)
points = set()
while len(points) < m:
x, y = random.sample(buffet, 2)
points.add((x, y)) if x > y else points.add((y, x)) # meeh
for (x, y) in points:
res += "%d %d\n" % (x, y);
return res
Upvotes: 3
Views: 2261
Reputation: 51998
Here is an approach which works by taking a number in the range 0 to n*(n-1)/2 - 1
and decodes it to a unique pair of items in the range 0 to n-1
. I used 0-based math for convenience, but you could of course add 1 to all of the returned pairs if you want:
import math
import random
def decode(i):
k = math.floor((1+math.sqrt(1+8*i))/2)
return k,i-k*(k-1)//2
def rand_pair(n):
return decode(random.randrange(n*(n-1)//2))
def rand_pairs(n,m):
return [decode(i) for i in random.sample(range(n*(n-1)//2),m)]
For example:
>>> >>> rand_pairs(5,8)
[(2, 1), (3, 1), (4, 2), (2, 0), (3, 2), (4, 1), (1, 0), (4, 0)]
The math is hard to easily explain, but the k
in the definition of decode
is obtained by solving a quadratic equation which gives the number of triangular numbers which are <= i
, and where i
falls in the sequence of triangular numbers tells you how to decode a unique pair from it. The interesting thing about this decode is that it doesn't use n
at all but implements a one-to-one correspondence from the set of natural numbers (starting at 0) to the set of all pairs of natural numbers.
Upvotes: 3
Reputation: 7510
You can use combinations
to generate all pairs and use sample
to choose randomly. Admittedly only lazy in the "not much to type" sense, and not in the use a generator not a list sense :-)
from itertools import combinations
from random import sample
n = 100
sample(list(combinations(range(1,n),2)),5)
If you want to improve performance you can make it lazy by studying this Python random sample with a generator / iterable / iterator
the generator you want to sample from is this: combinations(range(1,n)
Upvotes: 3
Reputation: 7221
I don't think any thing on your line can improve. After all, as your m
get closer and closer to the limit n(n-1)/2
, you have thinner and thinner chance to find the unseen pair.
I would suggest to split into two cases: if m
is small, use your random approach. But if m
is large enough, try
pairs = list(itertools.combination(buffet,2))
ponits = random.sample(pairs, m)
Now you have to determine the threshold of m
that determines which code path it should go. You need some math here to find the right trade off.
Upvotes: 1