Loops

Note

There’s a saying: “In C, loops are your friend. In Python, loops are your enemy.”

C++ is a compiled language. In the process of turning C++ code into machine language, the compiler can do many things to automatically optimize your code; for example, it can identify small loops (like the one in our example code fragment) and arrange for it to be executed in a CPU’s memory cache.

Python can’t do this. It has to interpret each line one-by-one. In a loop, it goes through each line, then “backtracks” to the beginning of the loop and freshly interprets each line again.

There are some tricks to get around this. Mostly they involve using Python to call routines written in C.1

As a first step, let’s get rid of one “cheat” that I put into our code fragements. The while statement has its uses, but they’re associated with potentially varying logical conditions like reading a file. If you’re incrementing a number by a constant interval, then both Python and C++ have a better way to write a loop.2

Listing 48: A for loop in Python
# For each user, add a displacement to the distance array
interval = 10
scale = interval + 3
limit = interval + 1
# Assuming we want j=0:
for j in range(0,limit):  
   distance[j] += scale*j
Listing 49: A for loop in C++
// For each user, add a displacement to the distance array
int interval = 10;
int scale = interval + 3;
int limit = interval + 1;
// Assuming that we want j=0:
for ( int j=0; j < limit; ++j ) {
   distance[j] += scale*j
}

There’s not much more we can do for the C++ code,3 so we’ll focus on the Python version of the rest of this section.

I didn’t supply a defintion for distance. Given the use of distance[j], it could be simple Python array. But if you’ve used Python before, you’re probably screaming at me: “Use numpy!”

The length of the distance array isn’t specified in the code fragment. In theory, it could be larger than limit. Let’s be general for the moment, and assume the length of distance is greater than limit.

import numpy as np
distances = 20 # or some other value
# Create a numpy array of length 'distances'
distance = np.zeros((distances))

Given the artificial nature of the original code fragment, the fastest way to perform this task is to use numpy’s array features:

Listing 50: The Python code using numpy
# For each user, add a displacement to the distance array
interval = 10
scale = interval + 3
limit = interval + 1
j = 0 # or previously calculated elsewhere
distance[j:limit] += scale * np.arange(j,limit)

This will give us C-level speed. It’s up to you to decide whether this gives us Python-level clarity.4

Here’s a more detailed tutorial on using numpy to perform faster computations.

xkcd good_code

Figure 98: https://xkcd.com/844/ by Randall Munroe


1

At this point in the tutorial, you already know that I’m biased in favor of C++ over Python. So I can’t help but be snide and point out that if you’re using Python to call C, write not write your code in C in the first place?

The usual reason to prefer Python is its development cycle: You can quickly test small fragments and integrate them into your larger program. But now you know ROOT, which has a C++ interpreter that lets you do the same thing.

With that foolishness off my chest, I will continue to support and inform your use of Python. I’ve got at least one more lifetime I can spend learning more about the language!

2

I’m now using a lower limit of j=0 in the loop, which I did not explictly do when I first introduced the fragments. In the “real” world, if the value of j was more complicated, it would not change the loop code by much… except that I would not have continued to use the simple letter j for the name of the variable.

3

Though as we’ll see in the next section, there’s a way to improve the C++ compilation, as opposed to the code.

4

Although we’ve achieved faster code, we haven’t necessarily achieved more robust code. What if the value of limit is greater than the length of distance?