Reputation: 57
I am learning Python, and I would like to know if there are other functions, methods, or techniques to obtain a value from two ranges. I provide the only example I could came up with with my current knowledge. Any leads are quite appreciated:
import random
def choose_random_angle():
range1 = list(range(1, 76))
range2 = list(range(120, 231))
combined_ranges = range1 + range2
return random.choice(combined_ranges)
I was expecting to be able to include both ranges, separated by ";" or something along the lines. However, after reviewing the documentation, range function can only contain start, end, and step. The solution I have works, but I am sure there is a more efficient way.
Upvotes: 4
Views: 158
Reputation: 834
Testing a couple of solutions
import time
import random
import itertools
def test_function_efficiency(func, *args, num_iterations=10000):
start_time = time.perf_counter()
for _ in range(num_iterations):
func(*args)
end_time = time.perf_counter()
total_time = end_time - start_time
average_time = total_time / num_iterations
return average_time
# tuples with ranges
range1 = (1, 76, 1) # tuple(start, end, step)
range2 = (120, 231, 1)
def your_function(r1, r2):
return random.choice(list(range(*r1)) + list(range(*r2)))
def using_itertools_chain(r1, r2):
return random.choice(list(itertools.chain(range(*r1), range(*r2))))
# select a range and then choose a random number within that range
def direct_choose(r1, r2):
return random.randrange(*random.choice((r1, r2))) # equal weights
def direct_choose_weighted(r1, r2):
size_r1 = r1[1] - r1[0] if len(r1) == 3 and r1[2] == 1 else len(range(*r1))
size_r2 = r2[1] - r2[0] if len(r2) == 3 and r2[2] == 1 else len(range(*r2))
weights = [size_r1, size_r2]
return random.randrange(*random.choices([r1, r2], weights=weights)[0]) # weighted by range size
def direct_choose_multiple_ranges(*ranges): # accept multiple range tuples
return random.randrange(*random.choice(ranges)) # not weighted
# Test the functions
avg_time_your_function = test_function_efficiency(your_function, range1, range2)
avg_time_itertools_chain = test_function_efficiency(using_itertools_chain, range1, range2)
avg_time_direct = test_function_efficiency(direct_choose, range1, range2)
avg_time_direct_weighted = test_function_efficiency(direct_choose_weighted, range1, range2)
avg_time_ranges = test_function_efficiency(direct_choose_multiple_ranges, range1, range2)
print(f"Average time (your function): {avg_time_your_function * 1000:.8f} milliseconds")
print(f"Average time (itertools chain): {avg_time_itertools_chain * 1000:.8f} milliseconds")
print(f"Average time (direct choose): {avg_time_direct * 1000:.8f} milliseconds")
print(f"Average time (direct choose weighted): {avg_time_direct_weighted * 1000:.8f} milliseconds")
print(f"Average time (direct choose multiple ranges): {avg_time_ranges * 1000:.8f} milliseconds")
Got these results
range1 = (1, 76, 1)
range2 = (120, 231, 1)
Average time (your function): 0.01118883 milliseconds
Average time (itertools chain): 0.01489987 milliseconds
Average time (direct choose): 0.00362538 milliseconds
Average time (direct choose weighted): 0.01429519 milliseconds
Average time (direct choose multiple ranges): 0.00363713 milliseconds
range1 = (1, 1000, 2)
range2 = (1200, 1500, 1)
Average time (your function): 0.05656854 milliseconds
Average time (itertools chain): 0.05776611 milliseconds
Average time (direct choose): 0.00399395 milliseconds
Average time (direct choose weighted): 0.01802900 milliseconds
Average time (direct choose multiple ranges): 0.00419025 milliseconds
With bigger range sizes, direct selection, whether weighted or not, seems to be the most effective method.
Upvotes: 0
Reputation: 102529
You can use random.binomialvariate
(added in version 3.12.)
import random
r1 = range(1, 76)
r2 = range(120, 231)
p = len(r2)/(len(r1)+len(r2))
random.choice([r1,r2][random.binomialvariate(1, p)])
Upvotes: 1
Reputation: 107015
@MarkTolonen's answer is great for the specific case in the question but could use a bit of generalization for any two given ranges:
import random
def choose_random_angle(range1, range2):
border = len(range1)
pick = random.randrange(border + len(range2))
if pick < border:
picked_range = range1
else:
pick -= border
picked_range = range2
return picked_range[pick]
print(choose_random_angle(range(1, 76), range(120, 231)))
Upvotes: 1
Reputation: 10465
You could combine them once:
import random
import functools
choose_random_angle = functools.partial(
random.choice,
(*range(1, 76), *range(120, 231))
)
Upvotes: 1
Reputation: 178021
For this specific example, you could use a single range, and skip the gap, e.g.:
import random
def choose_random_angle():
a = random.randint(1, 186)
return a if a < 76 else a + 44
So 1-75 returns 1-75, and 76-186 returns 120-230.
This would be more efficient then generating a large list of numbers.
Upvotes: 6