MicroPython 'for' Loops'

Contents


Introduction

MicroPython is a “slim” version of Python specifically designed with a small footprint to efficiently run on memory constrained microcontrollers.

Computer languages provide a variety of statement types. Programmers would expect as a minium to find:

This article explores MicroPython's for looping statement.

The for Loop

A for loop is a type of loop that runs for a preset number of times. It also has the ability to iterate over the items of any sequence, such as a list or a string. The continue and break control statements are also available for use in a for loop. All up, this makes the Python for construct very powerful.

Syntax

for i in <collection>: 
  <loop body>

          

The for Loop with Numbers

The easiest way to use a for with numbers is with the MicroPython range() function.

Python range() function

The range() function returns a sequence of numbers, starting from 0 by default, and increments by 1 (by default), and stops immediately before a specified number.

The full syntax is:


range([start,] stop[, step])

            
Table 1: range() Function Parameters
start Optional. An integer number specifying at which position to start. Default is 0
stop Required. An integer number specifying at which position to stop immediately before.
step Optional. An integer number specifying the incrementation. Default is 1

When discussing while loops in the previous tutorial in this series, an example was given that summed the first 10,000 integers.

A much more machine efficient version can be written with a for loop.

Example 1

sum = 0 #initialise
for counter in range(1, 10001): sum += counter
print('Sum of 1..10,000 is', sum)

          
Output:

Sum of 1..10,000 is 50005000

          

It is an easy exercise to demonstrate the improvement gained with the use of a for loop in preference to a while loop when the number of loop iterations is known in advance.

Example 2

# Demonstrate that 'for' loops are more
# efficient than 'while' loops if the
# number of loop iterations are known
# in advance.

# Program sums first 1,000,000 integers using
# a 'for' loop then a 'while' loop.
# Both loops are timed.

# MicroPython library for timing functions.
import time

# Functions to use micro:bit hardware.
import microbit

# Time the 'for' loop
sum1 = 0
start = time.ticks_ms() # Get current time
for counter in range(1, 1000001): sum1 += counter
elapsed1 = time.ticks_ms() - start

# Time the 'while' loop
counter = 1
sum2 = 0
start = time.ticks_ms() # Get current time
while (counter <= 1000000):
    sum2 += counter
    counter += 1
elapsed2 = time.ticks_ms() - start

#Print the comparison
print("Loop using 'for'")
print('Sum of 1..1,000,000 is', sum1)
print('Time taken is', elapsed1/1000, 'seconds')    
print()

print("Loop using 'while'")
print('Sum of 1..1,000,000 is', sum2)
print('Time taken is', elapsed2/1000, 'seconds')
print()

eff = (elapsed2 - elapsed1)/elapsed2 * 100
print("Improvement using 'for' loop is", eff, "%")

# Turn on LED display on micro:bit
# Signifies program has completed.
microbit.display.show(microbit.Image.SMILE)
          

This program was run on a micro:bit. It takes the time for the summation of the first 1,000,000 integers firstly using a for loop then repeats the exercise using a while loop.

The percentage efficiency gain is calculated and was found to be around 8%. This could be a significant improvement, for example, on a microprocessor running a critical real-time process. Output is shown below.

Output:

Loop using 'for'
Sum of 1..1,000,000 is 500000500000
Time taken is 45.421 seconds

Loop using 'while'
Sum of 1..1,000,000 is 500000500000
Time taken is 49.394 seconds

Improvement using 'for' loop is 8.043488 %

          

The for Loop with Iterable Data Types

It is an easy task to loop through any iterable data type, retrieving each element, one after the other. MicroPython iterable data types include list, set, dictionary, string and tuple

Syntax

for i in <collection>:
    <loop body>

Each time through the loop, the variable i takes
on the value of the next object in collection.
          
Example 3

# Demonstrates how a 'for' loop can access all
# elements of an iterable data type in sequence.

# Program prints each element of various
# iterable data types - list, set,
# dictionary, string and tuple.

def PrintElements(var, var_type):
    print(var_type, 'example... ', end="")
    for element in var:
        # Print all elements of the iterable
        # on a single line.
        print(element, end=' ')
    print()

# list example
list1 = ['AB', 'CD', 'EF', 'GH', 'AB']
PrintElements(list1, 'list')

# set example
set1 = set(list1)
PrintElements(set1, 'set')

# dictionary example
dict1 = {'first':1, 'second':2, 'third':3}
PrintElements(dict1, 'dictionary')

# string example
string1 = 'Hello World'
PrintElements(string1, 'string')

# tuple example
tuple1 = tuple(list1)
PrintElements(tuple1, 'tuple')
          

This code tests the common Python iterable data types as mentioned above.

For code brevity a user defined function is used to implement the for loop. User defined functions are covered here. Note how the single function is able to handle all of these data types.

The program was executed on a micro:bit from the Mu Editor.

Output:

list example... AB CD EF GH AB 
set example... EF GH AB CD 
dictionary example... second first third 
string example... H e l l o   W o r l d 
tuple example... AB CD EF GH AB 
          

List Comprehension

List comprehension is a useful shortcut technique to create a new list based from the data of an existing list.

Syntax

newlist = [<expression> for <loop variable> 
           in <list> (if condition)]

Here, expression can be a piece of code , for example a method, that returns a value. The elements of list will be appended to the newlist array if loop variable fulfills the condition.

Try the following on a micro:bit.

Example 4

# List comprehension example

# A new list ('newlist') is populated
# with all values that are even from
# a list of integers ('biglist').

# All negative values that meet the 
# condition (even number) are converted to
# positive values before inserting into newlist.

biglist = [12, -57, 23, 45, -2, 98, -26]
newlist = [abs(i) for i in biglist if (i % 2 == 0)]
print('The new list is', newlist)

Output:
The new list is [12, 2, 98, 26]

Each value from the list of integers, biglist, is examined in turn. If the integer is an odd number then it is rejected. If it is an even number then it is converted to a positve value (if negative) and appended to newlist. This is all done in a single line of code!

The for-else Loop

In exactly the same manner as the while statement, an else may be appended to a for statement. The else code block will be run once the main loop has completed.

The control statements continue and break also apply with the for loop, exactly as they do for a while loop

A break statement will cause an else block (if present) to be skipped.

Syntax

for i in <collection>: 
  <loop body>
else: 
  <code block>

The else block will run after
the for loop completes.

Example 5

# Demonstrates for.. else.. loop.

# Inverts the case of all letters in a string
# Ignores characters from a given set ('ignore')

string1 = 'I Saw: - ##*A Striped Zebra*##'
# Characters that are to be ignored
ignore = {':', '*', '#'} 

print('Original string: ', string1)
print('Converted string: ', end='')

for ch in string1:
    if (ch in ignore):
        # Ignore this character
        continue 
    if ch.isupper():
        # Uppercase, convert to lowercase
        print(ch.lower(), end='')
    elif ch.islower():
        # lowercase, convert to uppercase
        print(ch.upper(), end='')
    else:
        # Not a letter, no conversion
        print(ch, end='')
else:
    print('\nProgram ending...')

Output:

Original str:  I Saw: - ##*A Striped Zebra*##
Converted str: i sAW - a sTRIPED zEBRA

Note the use of continue to skip the second conditional statement if a character that must be ignored is found. The else block is still executed when the for loop is completed.

The next program adds a piece of extra functionality to the last example. When the fifth occurrence of a lowercase letter in the string being parsed is found then the for loop is immediately terminated.

Example 6

# Demonstrates for.. else.. loop.

# (1) Inverts the case of all letters
#     in a string.
# (2) Ignores characters from a given
#     set ('ignore')
# (3) Count lowercase letters found.
#     Stop the program on the fifth
#     occurrence of lowercase letters.


string1 = 'I Saw: - ##*A Striped Zebra*##'
# Characters that are to be ignored
ignore = {':', '*', '#'} 

# Lowercase letter count
lcount = 0

print('Original str:', string1)
print('Converted str:', end='')

for ch in string1:
    if (ch in ignore):
        # Ignore this character
        continue 
    if ch.isupper():
        # Uppercase, convert to lowercase
        print(ch.lower(), end='')
    elif ch.islower():
        lcount += 1
        # Stop program if this is the fifth
        # occurence of a lowercase letter.
        if (lcount == 5):
            print()
            break
        # Convert lowercase to uppercase
        print(ch.upper(), end='')
    else:
        # Not a letter, no conversion
        print(ch, end='')
else:
    print('\nProgram ending...')

Output:

Original str: I Saw: - ##*A Striped Zebra*##
Converted str: i sAW - a sTR

The fifth occurrence of a lowercase letter occurs with the ‘i’ in the word ‘Striped’. Execution of the for loop immediately terminates. Note that this also prevents the else code from running.

Nested Loops

Both while and for loops can be nested within each other to any practical depth and in any combination. Care needs to be taken though that readability doesn't overly suffer.

The following program finds and prints the prime numbers between a given inclusive range (100..150). The program demonstrates the use of a nested for loop inside the main for loop.

Example 7

# Demonstrates a 'for' loop nested in
# another 'for' loop.

# Returns all prime numbers inclusive between
# two given values ('low' and 'high').

low = 100
high = 150

print('Prime numbers between',
       low, 'and', high, 'are:')

for num in range(low, high + 1):
    # Check if 'num' can be divided by
    # anything other than 1 or itself
    # while leaving no remainder.
    for divisor in range(2, num//2 + 1):
        if (num % divisor == 0):
            # 'num' is not a prime
            break
    else:
        # 'num' is a prime
        print(num, end=' ')

print()

Output:

Prime numbers between 100 and 150 are:
101 103 107 109 113 127 131 137 139 149

Each number in the range is taken one by one and checked whether it can be divided evenly by any number other than one (1) or itself. This work is done in the nested for loop.

The next program also returns prime numbers. This time the first ‘n’ primes are returned. Specifically the program has n = 15, but could be easily modified to accept a value input by a user.

Example 8

# Demonstrates a 'for' loop nested
# in a 'while' loop.

# Program returns the first 'n' prime numbers.

# Number of prime numbers required.
n = 15

# Start point for prime number search
num = 2
# Number of prime number found
count = 0

print('The first', n, ' prime numbers are:')

while (count < n):
    # Check if 'num' can be divided by
    # anything other than 1 or itself
    # while leaving no remainder.
    for divisor in range(2, num//2 + 1):
        if (num % divisor == 0):
            # 'num' not a prime
            break
    else:
        # 'num' is prime
        print(num, end=' ')
        count += 1
    num += 1

print()


The first 15  prime numbers are:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

This time there is a nested for loop within the main while loop. The outer while loop continues till the required number of primes are found. The nested for loop incrementally tests numbers starting at two (2) for prime number membership. The algorithm is the same as used in Example 6.