Python’s built-in list data structure has many powerful methods any advanced Python programmer must be familiar with. However, some operations on lists can’t be performed simply by calling the right method.
You can add a single item to a list using the method append(item)
on the list. If you want to add a list of items to another list, there is the method expand(items)
which does the job for you.
The same holds if you want to delete an item from a list, you simply call the method remove(item)
and you get the desired outcome.
But, did you ever wonder how to delete a list of items from a given list? Or what if the indices of the items to be deleted were given, how would you do that?
These were the questions I was asking myself in one of my latest hobby projects. Therefore I decided to find out the most Pythonic way to do that.
Problem
Let’s frame our problem like this: Given a list of Task items, how can we remove all items from the list which are marked as done?
Currently the implementation looks as follows:
class Task: def __init__(self, title): self.title = title self.done = False self.done_by = None def is_done(self): return self.done def set_done(self, name): self.done = True self.done_by = name def __repr__(self): state = f'was done by {self.done_by}' if self.done else 'is not done' s = f'Task: {self.title} {state}' return s todo_list = [ Task('Clean House'), Task('Walk Dog'), Task('Buy Bread'), Task('Repair Car'), Task('Plant Tree'), Task('Water Flowers'), Task('Bake Cake') ] todo_list[0].set_done('Bob') todo_list[2].set_done('Alice') todo_list[5].set_done('Bob') # print the whole list print(todo_list)
So, how can we clean up our todo list so that it contains only tasks that have not yet been done?
Solutions
The following solutions can be divided into two groups:
- Delete elements of given indices
- Delete elements by a certain condition
Any solution of the first type can also be used to delete the elements by a given condition. To accomplish this, all we have to do, is iterate once over the input list, check the condition and store the indices of the elements for which the condition was True
. This can be implemented as follows:
indices = [] for idx, task in enumerate(todo_list): if task.is_done(): indices.append(idx)
Since it takes one iteration of the list to find the indices, this adds O(n) to the runtime complexity. Yet, since any solution has at least a time complexity of O(n), we can neglect this first step.
Method 1: Remove a Single Item From the List and Repeat in a Loop
As mentioned before, there are methods to remove a single item from a list, either by value or by index.
Therefore one solution to remove several items is to use a method that removes a single item and executes it in a loop. Though, there is a pitfall to this solution. After we remove the element at index 0, all the other elements shift, and their indices change because the element at index 1 is now at index 0 and so on.
This is how the solution would look as code:
1.1. Remove using pop()
The list.pop()
method removes and returns the last element from an existing list
. The list.pop(index)
method with the optional argument index
removes and returns the element at the position index
.
indices = [0, 2, 5] # must be ordered! shift = 0 for i in indices: todo_list.pop(i-shift) shift += 1
Well, probably this looks a bit awkward to you, and be reassured, it’s not the way you would do it in Python!
To avoid shifting, we can reverse sort the list of indices so that we can remove the items from end to start:
indices = [0, 2, 5] for i in sorted(indices, reverse=True): todo_list.pop(i)
1.2. Remove using remove()
A slightly simpler solution, but still not the best solution, uses the method re
move(item)
.
We iterate over the list and check for each item if it satisfied the condition so that it can be deleted. This solution would look like this:
for task in todo_list: if task.is_done(): todo_list.remove(task)
Be careful if you use remove(item)
on a list of simple data types like integers. The function remove()
deletes the first occurrence of the given value from the list!
In all of the above solutions, we performed the deletion in-place, which means, we kept the initial instance of the list.
By now you should see, a good solution to the problem is not that obvious.
1.3. Remove using itemgetter() and remove()
If you use the function itemgetter
from the module operator
there is another interesting solution which is basically an improvement of solution 1.1.
The function itemgetter
takes an arbitrary number of indices and returns all the elements from those indices in a tuple. Here is the implementation of the proposed solution:
from operator import itemgetter indices = [0, 2, 5] for item in (itemgetter(*idx)(todo_list)): xs.remove(item)
But still, the code is more complex than it needs to be.
Method 2. Remove Multiple Items from a List
In the previous solutions, we simply adapted functionality for deleting a single element so that we could use it inside a loop. In this section, we take a look at more Pythonic solutions for the problem.
2.1. Remove all elements from a list
If you want to remove all elements from the list, there is a very simple solution: Use the list class’s method clear()
. It removes all elements from the list in-place.
2.2. Remove a slice from a list
If your elements are in a continuous range or if they have a least equal distances from each other a simple way to delete multiple elements from a list is using the keyword del
together with slicing.
This could look like this:
del todo_list[1::2]
It deletes the elements in-place, however, it doesn’t help if we want to delete randomly distributed elements from our list.
2.3. Remove randomly distributed elements from a list using set operations
First, we iterate over the list once and extract all items to be deleted. Then, we convert both lists to sets and perform the removal using set operations. This looks as follows:
done = [] for task in todo_list: if task.is_done(): done.append(task) todo_list = list(set(todo_list) - set(done))
Under the hood, a set in Python is a hashmap that allows performing certain operations on sets very fast (O(1)). Unfortunately we have to convert from a list to a set and back, so that we loose the advantage in speed. And again, we end up with an O(n) solution.
This solution doesn’t work in-place and is a bit difficult to read due to the many conversions between data structures.
2.4. Remove randomly distributed elements from a list using list comprehension
The best way to do this in Python is actually very close to what we saw in the first section of this article where we iterated over the list and removed the elements for which a certain condition was True.
However, in this solution, we will proceed the other way round: We iterate over the old list and create a new list to which we add all the elements that we want to keep. Obviously, we have to create a new list to achieve this, so the solution won’t work in-place.
Python provides just what we need to get the desired result in one single line of code: list comprehensions.
todo_list = [task for task in todo_list if not task.is_done()]
If we assign the result of the list comprehension back to our initial todo_list
variable, this variable will now point to a list that contains only tasks that weren’t done yet.
After the above line of code, the memory address to which the variable todo_list
points has changed!
However, that’s how you should delete several elements from a list in Python. If you want to do this in-place, there is also a one-line solution to the problem, though, I personally wouldn’t recommend you to use this.
Here is the code:
[todo_list.remove(task) for task in todo_list if task.is_done()]
Be honest, how long did you take to wrap your head around that?
We use a dummy list comprehension in which we delete the selected elements from the initial list, finally we throw away the list comprehension’s resulting list.
So, what we actually do is to abuse the list comprehension to iterate over todo_list
and delete items from it.
Conclusion
Depending on the distribution of the items in the list, there are different solutions.
- If you want to remove all elements from a list, use the list’s method
clear()
. - If you want to remove a continuous range from the list or if you want to delete items with equal distances between, use slicing with the operator
del l[start:stop]
. - If you want to remove randomly distributed elements, use a list comprehension which selects only the elements you want to keep – this is the solution I recommend.
Obviously, there are more possibilities to solve the problem, yet, the solutions presented in this article are the most common ones and also the easiest to understand. If you find another great solution, feel free to contact us! We would love to see it.
Where to Go From Here?
Enough theory, let’s get some practice!
To become successful in coding, you need to get out there and solve real problems for real people. That’s how you can become a six-figure earner easily. And that’s how you polish the skills you really need in practice. After all, what’s the use of learning theory that nobody ever needs?
Practice projects is how you sharpen your saw in coding!
Do you want to become a code master by focusing on practical code projects that actually earn you money and solve problems for people?
Then become a Python freelance developer! It’s the best way of approaching the task of improving your Python skills—even if you are a complete beginner.
Join my free webinar “How to Build Your High-Income Skill Python” and watch how I grew my coding business online and how you can, too—from the comfort of your own home.