How to generate exponentially increasing range in Python
Question:
I want to test the performance of some code using an exponentially increasing value. So that as an extra digit is added to the numbers_size the increment is multiplied by 10. This is how I’m doing it so far but it looks a bit hacky. Suggestions for improvements without introducing non-standard libraries?
numbers_size = 100
increment = 100
numbers_range = 1000000000
while numbers_size < numbers_range:
t = time.time()
test( numbers_size )
taken_t = time.time() - t
print numbers_size, test, taken_t
increment = 10 ** (len(str(numbers_size))-1)
numbers_size += increment
Answers:
example of ‘NOT reading the question properly’ and ‘NOT how to do it‘
for i in xrange(100, 1000000000, 100):
# timer
test(i)
# whatever
Is about as simple as it gets… adjust xrange
accordingly
The simplest thing to do is to use a linear sequence of exponents:
for e in range(1, 90):
i = int(10**(e/10.0))
test(i)
You can abstract the sequence into its own generator:
def exponent_range(max, nsteps):
max_e = math.log10(max)
for e in xrange(1, nsteps+1):
yield int(10**(e*max_e/nsteps))
for i in exponent_range(10**9, nsteps=100):
test(i)
Why not
for exponent in range(2, 10):
test(10 ** exponent)
if I’m reading your intent right.
Using a generator expression:
max_exponent = 100
for i in (10**n for n in xrange(1, max_exponent)):
test(i)
I like Ned Batcheldor’s answer, but I would make it a bit more general:
def exp_range(start, end, mul):
while start < end:
yield start
start *= mul
then your code becomes
for sz in exp_range(100, 1000000000, 10):
t = time.time()
test(sz)
print sz, test(sz), time.time()-t
To produce the same numbers as your code:
numbers_sizes = (i*10**exp for exp in range(2, 9) for i in range(1, 10))
for n in numbers_sizes:
test(n)
If you consider numpy as one of the standards ;), you may use numpy.logspace since that is what it is supposed to do…. (note: 100=10^2, 1000000000=10^9)
for n in numpy.logspace(2,9,num=9-2, endpoint=False):
test(n)
example 2 (note: 100=10^2, 1000000000=10^9, want to go at a step 10x, it is 9-2+1 points…):
In[14]: np.logspace(2,9,num=9-2+1,base=10,dtype='int')
Out[14]:
array([ 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000])
example 3:
In[10]: np.logspace(2,9,dtype='int')
Out[10]:
array([ 100, 138, 193, 268, 372,
517, 719, 1000, 1389, 1930,
2682, 3727, 5179, 7196, 10000,
13894, 19306, 26826, 37275, 51794,
71968, 100000, 138949, 193069, 268269,
372759, 517947, 719685, 1000000, 1389495,
1930697, 2682695, 3727593, 5179474, 7196856,
10000000, 13894954, 19306977, 26826957, 37275937,
51794746, 71968567, 100000000, 138949549, 193069772,
268269579, 372759372, 517947467, 719685673, 1000000000])
on your case, we use endpoint=False
since you want not to include the endpoint… (e.g. np.logspace(2,9,num=9-2, endpoint=False)
)
OP wrote “Suggestions for improvements without introducing non-standard libraries?”
Just for completeness, here’s a recipe for generating exponential ranges – each element is a fixed factor bigger than the previous:
from math import exp
from math import log
def frange(start, stop, numelements):
"""range function for floats"""
incr = (stop - start) / numelements
return (start + x * incr for x in range(numelements))
def exprange(start, stop, numelements):
"""exponential range - each element is a fixed factor bigger than the previous"""
return (exp(x) for x in frange(log(start), log(stop), numelements))
Test:
print(", ".join("%.3f" % x for x in exprange(3,81,6)))
Output:
3.000, 5.196, 9.000, 15.588, 27.000, 46.765
In case you don’t want to use any libraries or extra function definitions:
for n in [10**m for m in range(d)]:
print(n)
This list comprehension will do what you want.
d
is a number of digits. Convert to string and calculate the length if needed.
d = len(str(numbers_range))
I want to test the performance of some code using an exponentially increasing value. So that as an extra digit is added to the numbers_size the increment is multiplied by 10. This is how I’m doing it so far but it looks a bit hacky. Suggestions for improvements without introducing non-standard libraries?
numbers_size = 100
increment = 100
numbers_range = 1000000000
while numbers_size < numbers_range:
t = time.time()
test( numbers_size )
taken_t = time.time() - t
print numbers_size, test, taken_t
increment = 10 ** (len(str(numbers_size))-1)
numbers_size += increment
example of ‘NOT reading the question properly’ and ‘NOT how to do it‘
for i in xrange(100, 1000000000, 100):
# timer
test(i)
# whatever
Is about as simple as it gets… adjust xrange
accordingly
The simplest thing to do is to use a linear sequence of exponents:
for e in range(1, 90):
i = int(10**(e/10.0))
test(i)
You can abstract the sequence into its own generator:
def exponent_range(max, nsteps):
max_e = math.log10(max)
for e in xrange(1, nsteps+1):
yield int(10**(e*max_e/nsteps))
for i in exponent_range(10**9, nsteps=100):
test(i)
Why not
for exponent in range(2, 10):
test(10 ** exponent)
if I’m reading your intent right.
Using a generator expression:
max_exponent = 100
for i in (10**n for n in xrange(1, max_exponent)):
test(i)
I like Ned Batcheldor’s answer, but I would make it a bit more general:
def exp_range(start, end, mul):
while start < end:
yield start
start *= mul
then your code becomes
for sz in exp_range(100, 1000000000, 10):
t = time.time()
test(sz)
print sz, test(sz), time.time()-t
To produce the same numbers as your code:
numbers_sizes = (i*10**exp for exp in range(2, 9) for i in range(1, 10))
for n in numbers_sizes:
test(n)
If you consider numpy as one of the standards ;), you may use numpy.logspace since that is what it is supposed to do…. (note: 100=10^2, 1000000000=10^9)
for n in numpy.logspace(2,9,num=9-2, endpoint=False):
test(n)
example 2 (note: 100=10^2, 1000000000=10^9, want to go at a step 10x, it is 9-2+1 points…):
In[14]: np.logspace(2,9,num=9-2+1,base=10,dtype='int')
Out[14]:
array([ 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000])
example 3:
In[10]: np.logspace(2,9,dtype='int')
Out[10]:
array([ 100, 138, 193, 268, 372,
517, 719, 1000, 1389, 1930,
2682, 3727, 5179, 7196, 10000,
13894, 19306, 26826, 37275, 51794,
71968, 100000, 138949, 193069, 268269,
372759, 517947, 719685, 1000000, 1389495,
1930697, 2682695, 3727593, 5179474, 7196856,
10000000, 13894954, 19306977, 26826957, 37275937,
51794746, 71968567, 100000000, 138949549, 193069772,
268269579, 372759372, 517947467, 719685673, 1000000000])
on your case, we use endpoint=False
since you want not to include the endpoint… (e.g. np.logspace(2,9,num=9-2, endpoint=False)
)
OP wrote “Suggestions for improvements without introducing non-standard libraries?”
Just for completeness, here’s a recipe for generating exponential ranges – each element is a fixed factor bigger than the previous:
from math import exp
from math import log
def frange(start, stop, numelements):
"""range function for floats"""
incr = (stop - start) / numelements
return (start + x * incr for x in range(numelements))
def exprange(start, stop, numelements):
"""exponential range - each element is a fixed factor bigger than the previous"""
return (exp(x) for x in frange(log(start), log(stop), numelements))
Test:
print(", ".join("%.3f" % x for x in exprange(3,81,6)))
Output:
3.000, 5.196, 9.000, 15.588, 27.000, 46.765
In case you don’t want to use any libraries or extra function definitions:
for n in [10**m for m in range(d)]:
print(n)
This list comprehension will do what you want.
d
is a number of digits. Convert to string and calculate the length if needed.
d = len(str(numbers_range))