Get startedGet started for free

Efficiently combining, counting, and iterating

1. Efficiently combining, counting, and iterating

In this lesson, we'll cover combining, counting, and iterating over objects efficiently. Let's begin by exploring a dataset from the popular Nintendo video game Pokémon.

2. Pokémon Overview

The Pokémon game centers around players called trainers that try to collect fictional animals called Pokémon.

3. Pokémon Overview

These Pokémon animals roam the fictional universe where the game takes place. When a trainer encounters a Pokémon, they try to capture that Pokémon to add to their collection.

4. Pokémon Overview

If a trainer successfully captures a Pokémon, it is stored in a tool called a Pokédex.

5. Pokémon Description

Each Pokémon comes with its own set of metadata.

6. Pokémon Description

This metadata contains a name for each Pokémon. It also has the generation of each Pokémon specifying what version of the game the Pokémon appears in. Here, Squirtle, a Pokémon from generation one, is shown.

7. Pokémon Description

The metadata also includes the Pokémon's Type and whether or not it belongs to a special category called Legendary.

8. Pokémon Description

Each Pokémon has a set of statistics that are numerical values for certain categories like Health Points (called HP), Attack, and others. We'll use a dataset that contains pieces of this metadata for the remainder of the chapter.

9. Combining objects

Suppose we have two lists: one of Pokémon names and another of each Pokémon's Health Points. We want to combine these lists so that each Pokémon is stored next to its Health Points. We can iterate over the names list using enumerate and grab each Pokémon's corresponding Health Points using the index variable.

10. Combining objects with zip

But Python's built-in function zip provides a more elegant solution. The name "zip" describes how this function combines objects like a zipper on a jacket (making two separate things become one). zip returns a zip object that must be unpacked into a list and printed to see the contents. Each item is a tuple of elements from the original lists.

11. The collections module

Python comes with a number of efficient built-in modules. The collections module contains specialized datatypes that can be used as alternatives to standard dictionaries, lists, sets, and tuples.

12. The collections module

A few notable specialized datatypes are listed here. Let's dig deeper into the Counter object.

13. Counting with loop

Our Pokémon dataset describes 720 characters. Here is a list of each Pokémon's corresponding type. We'd like to create a dictionary where each key is a Pokémon type, and each value is the count of characters that belong to that type. Using a standard dictionary approach, we have to instantiate an empty output dictionary. Then, we iterate over the poke_types list and check whether or not each poke_type exists within the type_counts dictionary. If the poke_type is not in the dictionary, we create a new key and initialize its count value as one. If the poke_type is already in the dictionary, we update the count by one.

14. collections.Counter()

Using Counter from the collections module is a more efficient approach. Just import Counter and provide the object to be counted. No need for a loop! Counter returns a Counter dictionary of key-value pairs. When printed, it's ordered by highest to lowest counts. If comparing runtime times, we'd see that using Counter takes half the time as the standard dictionary approach!

15. The itertools module

Another built-in module, itertools, contains functional tools for working with iterators. A subset of these tools is listed here.

16. The itertools module

We'll focus on one piece of this module: the combinatoric generators. These generators efficiently yield Cartesian products, permutations, and combinations of objects. Let's explore an example.

17. Combinations with loop

Suppose we want to gather all combination pairs of Pokémon types possible. We can do this with a nested for loop that iterates over the poke_types list twice. Notice that a conditional statement is used to skip pairs having the same type twice. For example, if x is 'Bug' and y is 'Bug', we want to skip this pair. Since we're interested in combinations (where order doesn't matter), another statement is used to ensure either order of the pair doesn't already exist within the combos list before appending it. For example, the pair ('Bug', 'Fire') is the same as the pair ('Fire', 'Bug'). We want one of these pairs, not both.

18. itertools.combinations()

The combinations generator from itertools provides a more efficient solution. First, we import combinations and then create a combinations object by providing the poke_types list and the length of combinations we desire. combinations returns a combinations object, which we unpack into a list and print to see the result. If comparing runtimes, we'd see using combinations is significantly faster than the nested loop.

19. Let's practice!

Let's put our skills to the test and practice combining, counting, and iterating!