Zipping and Unzipping Iterables in Python | by Luay Matalka | Towards Data Science

Let’s say that we have two lists, one that includes first names, and the other includes last names. We would like to somehow combine the first names with the corresponding last names as tuples. In other words, we would like to combine elements from multiple iterables that have the same index together in a list of tuples:
list_1 = [‘Jane’, ‘John’, ‘Jennifer’]list_2 = [‘Doe’, ‘Williams’, ‘Smith’]Desired Output:[('Jane', 'Doe'), ('John', 'Williams'), ('Jennifer', 'Smith')]
We can accomplish this with the zip() function, which is a built-in python function. The zip() function is named due to its analogous mechanism as physical zippers. When you zip something, you bring both sides together. And that’s how the zip() function works! It brings elements of the same index from multiple iterable objects together as elements of the same tuples.
zip(*iterables)
The zip() function takes in iterables as arguments, such as lists, files, tuples, sets, etc. The zip() function will then create an iterator that aggregates elements from each of the iterables passed in. In other words, it will return an iterator of tuples, where the i-th tuple will contain the i-th element from each of the iterables passed in. This iterator will stop once the shortest input iterable has been exhausted.
Well, based on the above goal, we have two lists (which are iterable objects), and we would like to combine the same indexed elements from each of these lists together. Thus, we can use the zip() function to accomplish this as follows:
first_names = [‘Jane’, ‘John’, ‘Jennifer’]last_names = [‘Doe’, ‘Williams’, ‘Smith’]full_names = list(zip(first_names, last_names))print(full_names)# [('Jane', 'Doe'), ('John', 'Williams'), ('Jennifer', 'Smith')]
Remember, the zip() function returns an iterator. Thus, we need to use the list() function that will use this returned iterator (or zip object) to create a list. In addition, as long as the iterables passed in are ordered (sequences), then the tuples will contain elements in the same left-to-right order of the arguments passed in the zip() function.
towardsdatascience.com
Let’s say we have another list, ages, that contains the age of the corresponding individual in the other two lists, first_names and last_names. We would like to also include the ages in in the tuple with the first and last name. Well, as mentioned above, the zip() function takes in any number of iterables.
first_names = ['Jane', 'John', 'Jennifer']last_names = ['Doe', 'Williams', 'Smith']ages = [20, 40, 30]names_and_ages = list(zip(first_names, last_names, ages))print(names_and_ages)# [('Jane', 'Doe', 20), ('John', 'Williams', 40), ('Jennifer', 'Smith', 30)]
Notice how the names_and_ages contains tuples with n elements (the same number of arguments, or iterable objects, we passed in to the zip() function).
If we only pass in one iterable object to the zip() function, then we will get a list of 1-item tuples as follows:
first_names = ['Jane', 'John', 'Jennifer']print(list(zip(first_names)))# [('Jane',), ('John',), ('Jennifer',)]
What if we pass in lists (or other iterable objects) of unequal lengths? In other words, let’s say that our last_names list contains 1 more element than first_names. Well, as mentioned above, the iterator returned by the zip() function will stop once the shortest input iterable has been exhausted. In other words, our list of tuples will only contain the elements from the indexes that are present in all the iterables passed in to the zip() function. Thus, the remaining elements in the longer iterables will be ignored.
first_names = [‘Jane’, ‘John’, ‘Jennifer’]last_names = [‘Doe’, ‘Williams’, ‘Smith’, 'Jones']full_names = list(zip(first_names, last_names))print(full_names)# [('Jane', 'Doe'), ('John', 'Williams'), ('Jennifer', 'Smith')]
If the elements in the longer iterables are needed, then we can use the itertools.zip_longest() (zip_longest() function located in the itertools module) function instead of zip(). It will continue until the longest iterable is exhausted, and will replace any missing values with the value passed in for the fillvalue argument (default is None).
We can use the zip() function to iterate in parallel over multiple iterables. Since the zip() function returns an iterator, we can use this zip object (the iterator it returns) in a for loop. And since with each iteration of this iterator a tuple is returned, we can unpack the elements of this tuple within the for loop:
first_names = ['Jane', 'John', 'Jennifer']last_names = ['Doe', 'Williams', 'Smith']for first, last in zip(first_names, last_names):    print(first, last)# Output:Jane DoeJohn WilliamsJennifer Smith
Or we can have three iterables:
first_names = [‘Jane’, ‘John’, ‘Jennifer’]last_names = [‘Doe’, ‘Williams’, ‘Smith’]ages = [20, 40, 30]for first, last, age in zip(first_names, last_names, ages):    print(f’{first} {last} is {age} years old’)# Output: Jane Doe is 20 years oldJohn Williams is 40 years oldJennifer Smith is 30 years old
Another example of parallel iteration:
We have two lists: a list of revenues and a list of costs. We would like to make a new list, profits, that is the difference between the revenues and costs. We can accomplish this using parallel iteration:
revenue = [30000, 50000, 70000, 90000]cost = [10000, 15000, 20000, 30000]profit = []total_profit = 0for revenue, cost in zip(revenue, cost):    profit.append(revenue — cost)    total_profit += revenue — costprint(profit)# [20000, 35000, 50000, 60000]print(total_profit)# 165000
towardsdatascience.com
Let’s say that we have the following list of tuples:
first_and_last_names = [('Jane', 'Doe'), ('John', 'Williams'), ('Jennifer', 'Smith')]
And we want to separate the elements in these tuples into two separate lists. Well, since that is the opposite of zipping (bringing things together), it would be unzipping (taking things apart). To unzip in python, we can use the unpacking operator * with the zip() function as follows:
first_names, last_names = zip(*first_and_last_names)first_names = list(first_names)last_names = list(last_names)print(first_names)# ['Jane', 'John', 'Jennifer']print(last_names)# ['Doe', 'Williams', 'Smith']
The unpacking operator * will unpack the first_and_last_names list of tuples into its tuples. These tuples will then be passed to the zip() function, which will take these separate iterable objects (the tuples), and combines their same-indexed elements together into tuples, making two separate tuples. Lastly, through tuple unpacking, these separated tuples will be assigned to the first_names and last_names variables. We then use the list() function to convert these tuples into lists.
For more on unpacking operators (* and **), iterables, iterators, and iteration, check out the following two blogs:
towardsdatascience.com
towardsdatascience.com
In this tutorial, we looked at how the zip() function works in python. We learned how the zip() function operates in different scenarios, such as with one iterable, or with iterables that have unequal lengths. We then saw how we can use the zip() function to iterate over multiple iterable objects in parallel. And lastly, we learned how to use the unpacking operator * to unzip objects in python.