DEV Community

Anurag Pandey
Anurag Pandey

Posted on

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)
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

Good ✔️

for name in names:
  print(name)
Enter fullscreen mode Exit fullscreen mode

But what if indices are required?🤔

for i in range(len(names)):
   print(i+1, names[i]) 
Enter fullscreen mode Exit fullscreen mode

No! ❌

Better way ✔️

for idx, name in enumerate(names, start=1):
  print(idx, name)
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Instead:

ans = sum(nums)
Enter fullscreen mode Exit fullscreen mode

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:

Discussion (0)