How To Eliminate Loops From Your Python Code
- Yajendra Prajapati
- Oct 13, 2023
- 6 min read
Using loops when writing Python code isn’t necessarily a bad design pattern but using extraneous loops can be inefficient and costly. Let’s look at some tools that can help us eliminate the need to use loops in our code.
Python comes with a few looping patterns that can be used when we want to iterate over an object’s contents:
For loops iterate over elements of a sequence piece-by-piece.
While loops execute a loop repeatedly as long as some Boolean condition is met.
Nested loops use multiple loops inside one another.
Although all of these looping patterns are supported by Python, we should be careful when using them. Because most loops are evaluated in a piece-by-piece manner, they are often inefficient solutions.
We should try to avoid looping as much as possible when writing efficient code. Eliminating loops usually results in fewer lines of code that are easier to interpret. One of the idioms of Pythonic code is that “flat is better than nested.” Striving to eliminate loops in our code will help us follow this idiom. In this article, we will discuss and explore a few easy techniques to get rid of the loops or at least write them in a better and more effective way

. Source - verywellfamily
1. Git Rid of the Loops
1.1. Eliminate Loops with List Comprehension, Map Function, & itertools
Suppose we have a list of lists, called poke_stats, that contains statistical values for each Pokémon. Each row corresponds to a Pokémon, and each column corresponds to a Pokémon’s specific statistical value. Here, the columns represent a Pokémon’s Health Points, Attack, Defense, and Speed. We want to do a simple sum of each of these rows in order to collect the total stats for each Pokémon. If we were to use a loop to calculate row sums, we would have to iterate over each row and append the row’s sum to the totals list. We can accomplish the same task, in fewer lines of code, with a list comprehension.
First, we will define the stats array:
# List of HP, Attack, Defense, Speed
poke_stats = [
[90, 92, 75, 60],
[25, 20, 15, 90],
[65, 130, 60, 75],
]
Now we will iterate through the elements of this array using for loop and print the timing:
%%timeit
totals = []
for row in poke_stats:
totals.append(sum(row))

We will do the same thing but using list comprehension:
%timeit totals_comp = [sum(row) for row in poke_stats]

Finally, we will iterate through the elements using the map function:
%timeit totals_map = [*map(sum, poke_stats)]

Each of these approaches will return the same list, but using a list comprehension or the map function takes one line of code, and has a faster runtime.
We’ve also covered a few built-in modules that can help us eliminate loops in the previous article. Instead of using the nested for loop, we can use combinations from the itertools module for a cleaner, more efficient solution.
poke_types = ['Bug', 'Fire', 'Ghost', 'Grass', 'Water']
# Nested for loop approach
combos = []
for x in poke_types:
for y in poke_types:
if x == y:
continue
if ((x,y) not in combos) & ((y,x) not in combos):
combos.append((x,y))
# Built-in module approach
from itertools import combinations
combos2 = [*combinations(poke_types, 2)]
1.2. Eliminate Loops with NumPy
Another technique for eliminating loops is to use the NumPy package. Suppose we had the same collection of statistics we used in a previous example but stored in a NumPy array instead of a list of lists.
We’d like to collect the average stat value for each Pokémon (or row) in our array. We could use a loop to iterate over the array and collect the row averages.
%%timeit
avgs = []
for row in poke_stats:
avg = np.mean(row)
avgs.append(avg)

But, NumPy arrays allow us to perform calculations on entire arrays all at once. Here, we use the .mean method and specify an axis equal to 1 to calculate the mean for each row (meaning we calculate an average across the column values). This eliminates the need for a loop and is much more efficient.
We can compare the timing of both methods with the code below:
%%timeitavgs = []
for row in poke_stats:
avg = np.mean(row)
avgs.append(avg)

%%timeit
avgs = poke_stats.mean(axis=1)
When comparing runtimes, we see that using the .mean method on the entire array and specifying an axis is significantly faster than using a loop.
2. Writing Better Loops
We’ve discussed how loops can be costly and inefficient. But, sometimes you can’t eliminate a loop. In this section, we’ll explore how to make loops more efficient when looping is unavoidable. Before diving in, some of the loops we’ll discuss can be eliminated using techniques covered in previous lessons. For demonstrative purposes, we’ll assume the use cases shown here are instances where a loop is unavoidable.
The best way to make a loop more efficient is to analyze what’s being done within the loop. We want to make sure that we aren’t doing unnecessary work in each iteration. If a calculation is performed for each iteration of a loop, but its value doesn’t change with each iteration, it’s best to move this calculation outside (or above) the loop. If a loop is converting data types with each iteration, it’s possible that this conversion can be done outside (or below) the loop using a map function. Anything that can be done once should be moved outside of a loop. Let’s explore a few examples.
2.1. Moving calculations above a loop We have a list of Pokémon names and an array of each Pokémon’s corresponding attack value. We’d like to print the names of each Pokémon with an attack value greater than the average of all attack values. To do this, we’ll use a loop that iterates over each Pokémon and its attack value. For each iteration, the total attack average is calculated by finding the mean value of all attacks. Then, each Pokémon’s attack value is evaluated to see if it exceeds the total average.
import numpy as npnames = ['Absol', 'Aron', 'Jynx', 'Natu', 'Onix']
attacks = np.array([130, 70, 50, 50, 45])
for pokemon,attack in zip(names, attacks):
total_attack_avg = attacks.mean()
if attack > total_attack_avg:
print(
"{}'s attack: {} > average: {}!"
.format(pokemon, attack, total_attack_avg) )

The inefficiency in this loop is the total_attack_avg variable being created with each iteration of the loop. But, this calculation doesn’t change between iterations since it is an overall average. We only need to calculate this value once. By moving this calculation outside (or above) the loop, we calculate the total attack average only once. We get the same output, but this is a more efficient approach.
import numpy as np
names = ['Absol', 'Aron', 'Jynx', 'Natu', 'Onix']
attacks = np.array([130, 70, 50, 50, 45])
# Calculate total average once (outside the loop)
total_attack_avg = attacks.mean()
for pokemon,attack in zip(names, attacks):
if attack > total_attack_avg:
print(
"{}'s attack: {} > average: {}!"
.format(pokemon, attack, total_attack_avg)
)

Let’s compare the runtimes of both methods:
%%timeit
for pokemon,attack in zip(names, attacks):
total_attack_avg = attacks.mean()
if attack > total_attack_avg:
print(
"{}'s attack: {} > average: {}!"
.format(pokemon, attack, total_attack_avg) )

%%timeit# Calculate total average once (outside the loop)
total_attack_avg = attacks.mean()
for pokemon,attack in zip(names, attacks):
if attack > total_attack_avg:
print(
"{}'s attack: {} > average: {}!"
.format(pokemon, attack, total_attack_avg)
)

We see that keeping the total_attack_avg calculation within the loop takes more than 120 microseconds.
2.2. Holistic Conversions
Another way to make loops more efficient is to use holistic conversions outside (or below) the loop. In the example below we have three lists from the 720 Pokémon dataset: a list of each Pokémon’s name, a list corresponding to whether or not a Pokémon has a legendary status, and a list of each Pokémon’s generation.
We want to combine these objects so that each name, status, and generation is stored in an individual list. To do this, we’ll use a loop that iterates over the output of the zip function. Remember, zip returns a collection of tuples, so we need to convert each tuple into a list since we want to create a list of lists as our output. Then, we append each individual poke_list to our poke_data output variable. By printing the result, we see our desired list of lists.
Lets us first define the data that we will be using:
import pandas as pd
pokemon = pd.read_csv('pokemon.csv')
names_list = pokemon['Name']
legend_status_list = pokemon['Legendary']
generations_list = pokemon['Generation']
Now let's run the loop and convert the tuples inside the loop and see the timing:
%%timeitpoke_data = []
for poke_tuple in zip(names_list, legend_status_list, generations_list):
poke_list = list(poke_tuple)
poke_data.append(poke_list)

However, converting each tuple to a list within the loop is not very efficient. Instead, we should collect all of our poke_tuples together, and use the map function to convert each tuple to a list. The loop no longer converts tuples to lists with each iteration. Instead, we moved this tuple to list conversion outside (or below) the loop. That way, we convert data types all at once (or holistically) rather than converting in each iteration.
%%timeitpoke_data_tuples = []
for poke_tuple in zip(names_list, legend_status_list, generations_list):
poke_data_tuples.append(poke_tuple)
poke_data = [*map(list, poke_data_tuples)]

Runtimes show that converting each tuple to a list outside of the loop is more efficient.
We have a selection of videos that can be helpful when you're applying for jobs or looking to enhance your skills:
1. HiDevs Community | Enhance Your Profile with Expert Mentorship & Real-World Projects
- Watch here: Video 1
2. HiDevs | Elevate Your Resume | LinkedIn Profile | Cover Letter
- Watch here: Video 2
3. HiDevs Community Platform Overview | Job Support | Resume Building | LinkedIn Enhancement
- Watch here: Video 3
4. HiDevs | Unlock Your Tech Future | Create a Standout Resume | Optimize LinkedIn | Get Job Assistance | Access Upskill Programs for Free
- Watch here: Video 4
We also have a collection of short videos that you may find valuable: Short Videos
Book a FREE OF COST 1:1 mentorship call with our Founder Mr. Deepak Chawla here,
here is his LinkedIn profile for more information.
Comments