To Loop or Not to Loop

"Code is read more often than it is written"
                                            Guido Van Rossum (Creator of Python)

Keep this thought in the back of your mind, as you go through this article.

Consider the following example:
We have a list of players, with their name and country, and we want to extract all Indian players.

players = [{'name': 'P V Sindhu', 'country': 'India'},
           {'name': 'Michael Phelps', 'country': 'USA'},
           {'name': 'Usain Bolt', 'country': 'Jamaica'},
           {'name': 'Manika Batra', 'country': 'India'}]

indian_players = []
for player in players:
  if player['country'] == 'India':
    indian_players.append(player)

Now, in the above code, each line is easily understood, but you have to cache four lines in the brain to understand the whole context.

indian_players = [player for player in players if player['country'] == 'India']

Using list comprehension we can write more expressible code.
The above code also doesn't violate the ITM [Initialize Then Modify] antipattern, where the variable is initialized first and then modified immediately after, as we did in the first example.

LOOP, IF LOOP, PROPER LOOP

Python provides some good abstractions over the looping construct, making code more expressible.

Accessing the elements in a list:

Bad ❌

names = ['P V Sindhu', 'Usain Bolt', 'Michael Phelps', 'Manika Batra']

for i in range(len(names)):
  print(names[i])

Good ✔️

for name in names:
  print(name)

But what if indices are required?🤔

for i in range(len(names)):
   print(i+1, names[i])

No! ❌

Better way ✔️

for idx, name in enumerate(names, start=1):
  print(idx, name)

Now, why is the second code better?
Firstly, using indices creates a level of indirection. If the value is required why bother about the index?

Secondly, what if names was not a list but a dictionary, or some other data structure where indexing is not possible?
In that case, the first code would fail as we are indexing in the collection, but the second one would still work correctly.

General rule of thumb, if you are using indices for iteration, Python may have already provided some better abstractions for it.

Some more examples

Iterating over keys and values in a dictionary:

turing_award_winners = {1966: 'Alan Perlis', 
                        1967: 'Maurice Wilkes',
                        1968: 'Richard Hamming',
                        1969: 'Marvin Minsky'}

for year in turing_award_winners:
  print(year, turing_award_winners[year])

Getting year, and then looking for name in dictionary again causes a level of indirection.

Instead:

for year, name in turing_award_winners.items():
  print(year, name)

It also provides clear meaning.

NOT LOOP

Many a time, looping should not be considered at all.
Consider a simple example of finding the sum of all numbers in a list.

Conventional:

nums = [1, 5, 11, 17, 23, 29]

ans = 0
for num in nums:
  ans += num

Instead:

ans = sum(nums)

The code conveys the idea, that ans contains the sum of all numbers in the list nums, and is also very concise.

Conclusion

Write more expressible code, by utilizing the different constructs that the language has to offer. Avoid raw loops whenever you can.

I highly recommend to watch the videos below.

Further Watch:

28