21

I am trying to loop between 0.01 and 10, but between 0.01 and 0.1 use 0.01 as the step, then between 0.1 and 1.0 use 0.1 as step, and between 1.0 and 10.0 use 1.0 as step.

I have the while loop code written, but want to make it more pythonic.

i = 0.01
while i < 10:
   # do something
   print i
   if i < 0.1:
       i += 0.01
   elif i < 1.0:
       i += 0.1
   else:
       i += 1

This will produce

0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 2, 3, 4, 5, 6, 7, 8, 9
4
  • 3
    put that in a function and add a yield i as last instruction of the loop. That would be a generator, which you can iterate Commented Jun 15, 2016 at 21:18
  • 6
    Watch out - incrementing by steps doesn't work very well with floating-point rounding constraints. You're likely to have boundary errors. Commented Jun 15, 2016 at 21:27
  • 1
    @njzk2 - yield should be the first instruction in his loop, where the # do something is now. His first value needs to be .01. Commented Jun 15, 2016 at 21:30
  • docs.python.org/2/library/decimal.html Commented Jun 16, 2016 at 6:33

7 Answers 7

21

A special-purse generator function might be the right way to go. This would effectively separate the boring part (getting the list of numbers right) from the interesting part (the # do something in your example).

def my_range():
    for j in .01, .1, 1.:
        for i in range(1, 10, 1):
            yield i * j

for x in my_range():
    print x
Sign up to request clarification or add additional context in comments.

4 Comments

This mostly works. You do get odd results like 0.30000000000000004 though.
Of course. That's the nature of floating-point arithmetic. 0.3 isn't representable in a float.
Yup, no surprise on my part. I made this comment for the OP.
You can get slightly better results at the expense of a slight bit of clarity by using division instead of multiplication: for j in 100., 10., 1.: for i in range(1, 10): yield i / j. It doesn't matter much. The important thing is to avoid floating-point steps, and you're already doing that.
3

One approach would be to use two loops: one for the order of magnitude, and one for the values from 1 to 9:

for exp in range(-2, 1):
    for i in range(1, 10):
        print("{:.2f}".format(i * 10 ** exp))

Comments

3

You could have a nested loop, the outer one that iterates over the precision and inner one that is just range(1,10):

for precision in (0.01, 0.1, 1):
    for i in range(1,10):
        i*=precision
        print(i)

However floats are probably not going to work in this case as this shows values like 0.30000000000000004 on my machine, for precise decimal values you would want to use the decimal module:

import decimal
for precision in ("0.01", "0.1", "1"):
    for i in range(1,10):
        i*=decimal.Decimal(precision)
        print(i)

1 Comment

Image
yeah fixed that now too, jeez my head just isn't in it today.
3

Just a single line of code through list comprehension -

for k in [i*j for j in (0.01, 0.1, 1) for i in range(1, 10)]

Can't be more pythonic!

2 Comments

You don't need the assignment. OP wants to use this to drive a loop: for x in (i*j for j in (.01, .1, 1.) for i in range(1,10)): print x
Image
:( it seems pretty contrived to have 3 for statements in a single line. I preferred it the other way.
2

Just in case you wished to replace the loop with vectorized code...

In [63]: np.ravel(10.**np.arange(-2, 1)[:,None] * np.arange(1, 10)[None,:])
Out[64]: 
array([ 0.01,  0.02,  0.03,  0.04,  0.05,  0.06,  0.07,  0.08,  0.09,
        0.1 ,  0.2 ,  0.3 ,  0.4 ,  0.5 ,  0.6 ,  0.7 ,  0.8 ,  0.9 ,
        1.  ,  2.  ,  3.  ,  4.  ,  5.  ,  6.  ,  7.  ,  8.  ,  9.  ])

2 Comments

Numpy is way overkill for this problem
Yes, my code can be regarded as kind of overkill if you just need to print out the numbers (BTW, mine isn't the only one NumPy-based solution proposed here). However, if the # do something block involves a call to a time-consuming function which takes the generated values as argument, I think the vectorized code might be more efficient. Anyway, it is simply another possible approach that some users of SO could find useful.
2

I'd recommend a generator function as well, but if the steps are not such convenient powers of each other I'd write it like

def my_range():
    i = 0
    while i < 0.1:
        i += 0.01
        yield i
    while i < 1:
        i += 0.1
        yield i
    while i < 10:
        i += 1
        yield i

for x in my_range():
    print x

It might be a bit more repetitive, but illustrates much better what is going on and that the yielded values are monotonically increasing (regardless what numbers you put in).

If it gets too repetitive, use

def my_range():
    i = 0
    for (end, step) in [(0.1, 0.01), (1, 0.1), (10, 1)]:
        while i < end:
            i += step
            yield i

2 Comments

Like any other assignment, += assignments are statements in Python; you can't yield them.
@user2357112: Right, fixed.
1

You could do something like:

import numpy as np
list1 = np.arange(0.01, 0.1, 0.01)
list2 = np.arange(0.1, 1, 0.1)
list3 = np.arange(1, 10, 1)
i_list = np.concatenate((list1, list2, list3))  # note the double parenthesis
for i in i_list:
    ...

Basically you create the entire list of values that you need up front, i_list, then just iterate through them in your for loop.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.