Queue & Stacks
queues and stacks
Last updated
queues and stacks
Last updated
If you often work with lists in Python, then you probably know that they don’t perform fast enough when you need to pop and append items on their left end. Python’s module provides a class called that’s specially designed to provide fast and memory-efficient ways to append and pop item from both ends of the underlying data structure.
Python’s deque
is a low-level and highly optimized that’s useful for implementing elegant, efficient, and Pythonic queues and stacks, which are the most common list-like data types in computing.
In this tutorial, you’ll learn:
How to create and use Python’s deque
in your code
How to efficiently append and pop items from both ends of a deque
How to use deque
to build efficient queues and stacks
When it’s worth using deque
instead of list
To better understand these topics, you should know the basics of working with Python . It’ll also be beneficial for you to have a general understanding of and .
Finally, you’ll write a few examples that walk you through some common use cases of deque
, which is one of Python’s most powerful data types.
Free Bonus: that shows you Python’s best practices with simple examples you can apply instantly to write more beautiful + Pythonic code.
deque
Appending items to and popping them from the right end of a Python list are normally efficient operations. If you use the for , then you can say that they’re O(1). However, when Python needs to reallocate memory to grow the underlying list for accepting new items, these operations are slower and can become O(n).
Additionally, appending and popping items on the left end of a Python list are known to be inefficient operations with O(n) speed.
Since Python lists provide both operations with and .pop()
, they’re usable as and . However, the performance issues you saw before can significantly affect the overall performance of your applications.
Python’s was the first data type added to the module back in . This data type was specially designed to overcome the efficiency problems of .append()
and .pop()
in Python list.
Deques are sequence-like data types designed as a generalization of stacks and queues. They support memory-efficient and fast append and pop operations on both ends of the data structure.
Note: deque
is pronounced as “deck.” The name stands for .
Deques are also the way to go if you need to keep a list of last-seen items because you can restrict the maximum length of your deques. If you do so, then once a deque is full, it automatically discards items from one end when you append new items on the opposite end.
Here’s a summary of the main characteristics of deque
:
Doesn’t support slicing, like in a_deque[0:2]
Supports normal and reverse iteration
Ensures fast, memory-efficient, and thread-safe pop and append operations on both ends
Creating deque
instances is a straightforward process. You just need to import deque
from collections
and call it with an optional iterable
as an argument:>>>
The deque
initializer takes the following two optional arguments:
iterable
holds an iterable that provides the initialization data.
Here, you use .popleft()
and .appendleft()
to remove and add values, respectively, to the left end of numbers
. These methods are specific to the design of deque
, and you won’t find them in list
.
Here, .pop()
removes and returns the last value in the deque. The method doesn’t take an index as an argument, so you can’t use it to remove arbitrary items from your deques. You can only use it to remove and return the rightmost item.
Doubly linked lists make appending and popping items from either end light and efficient operations. That’s possible because only the pointers need to be updated. As a result, both operations have similar performance, O(1). They’re also predictable performance-wise because there’s no need for reallocating memory and moving existing items to accept new ones.
Appending and popping items from the left end of a regular Python list requires shifting all the items, which ends up being an O(n) operation. Additionally, adding items to the right end of a list often requires Python to reallocate memory and copy the current items to the new memory location. After that, it can add the new items. This process takes longer to complete, and the append operation passes from being O(1) to O(n).
Consider the following performance tests for appending items to the left end of a sequence, deque
vs list
:
In this specific example, .appendleft()
on a deque
is several times faster than .insert()
on a list
. Note that deque.appendleft()
is O(1), which means that the execution time is constant. However, list.insert()
on the left end of the list is O(n), which means that the execution time depends on the number of items to process.
In this example, if you increment the value of TIMES
, then you’ll get higher time measurements for list.insert()
but stable (constant) results for deque.appendleft()
. If you’d like to try a similar performance test on pop operations for both deques and lists, then you can expand the exercise block below and compare your results to Real Python‘s after you’re done.
Exercise: Test deque.popleft()
vs list.pop(0)
performanceShow/Hide
Solution: Test deque.popleft()
vs list.pop(0)
performanceShow/Hide
The deque
data type was designed to guarantee efficient append and pop operations on either end of the sequence. It’s ideal for approaching problems that require the implementation of queue and stack data structures in Python.
deque
Python’s deque
returns mutable sequences that work quite similarly to lists. Besides allowing you to append and pop items from their ends efficiently, deques provide a group of list-like methods and other sequence-like operations to work with items at arbitrary locations. Here are some of them:
Insert an item value
into a deque at index i
.
Retrieve the item at index i
from a deque.
Remove the item at index i
from a deque.
You can use these methods and techniques to work with items at any position inside a deque
object. Here’s how to do that:>>>
Deques support indexing, but interestingly, they don’t support slicing. When you try to get a slice from a deque, you get a TypeError
. In general, performing a slicing on a linked list would be inefficient, so the operation isn’t available.
Here’s a script that shows how deques and lists behave when it comes to working with arbitrary items:
This script times inserting, deleting, and accessing items in the middle of a deque and a list. If you run the script, then you get an output that looks like the following:
Deques aren’t random-access data structures like lists. Therefore, accessing elements from the middle of a deque is less efficient than doing the same thing on a list. The main takeaway here is that deques aren’t always more efficient than lists.
Python’s deque
is optimized for operations on either end of the sequence, so they’re consistently better than lists in this regard. On the other hand, lists are better for random-access and fixed-length operations. Here are some of the differences between deques and lists in terms of performance:
Operation
deque
list
Accessing arbitrary items through indexing
O(n)
O(1)
Popping and appending items on the left end
O(1)
O(n)
Popping and appending items on the right end
O(1)
O(1) + reallocation
Inserting and deleting items in the middle
O(n)
O(n)
In the case of lists, .append()
has amortized performance affected by memory reallocation when the interpreter needs to grow the list to accept new items. This operation requires copying all the current items to the new memory location, which significantly affects the performance.
deque
If you’re working with queues, then favor using those high-level abstractions over deque
unless you’re implementing your own data structure.
To better understand queues, take your favorite restaurant as an example. The restaurant has a queue of people waiting for a table to order their food. Typically, the last person to arrive will stand at the end of the queue. The person at the beginning of the queue will leave it as soon as a table is available.
Here’s how you can emulate the process using a bare-bones deque
object:>>>
For example, say you need a custom queue abstract data type that provides only the following features:
Enqueuing items
Dequeuing items
Returning the length of the queue
Supporting membership tests
Supporting normal and reverse iteration
Providing a user-friendly string representation
In this case, you can write a Queue
class that looks like the following:
Here, ._items
holds a deque
object that allows you to store and manipulate the items in the queue. Queue
implements .enqueue()
using deque.append()
to add items to the end of the queue. It also implements .dequeue()
with deque.popleft()
to efficiently remove items from the beginning of the queue.
Length with len()
Membership tests with in
Normal iteration
Reverse iteration
String representation
Ideally, .__repr__()
should return a string representing a valid Python expression. This expression will allow you to recreate the object unambiguously with the same value.
With these final additions, your Queue
class is complete. To use this class in your code, you can do something like the following:>>>
deque
In addition to the features you’ve seen so far, deque
also provides other methods and attributes specific to their internal design. They add new and useful functionalities to this versatile data type.
In this section, you’ll learn about other methods and attributes that deques provide, how they work, and how to use them in your code.
maxlen
One of the most useful features of deque
is the possibility to specify the maximum length of a given deque using the maxlen
argument when you’re instantiating the class.
If you supply a value to maxlen
, then your deque will only store up to maxlen
items. In this case, you have a bounded deque. Once a bounded deque is full with the specified number of items, adding a new item at either end automatically removes and discards the item at the opposite end:>>>
If the number of items in the input iterable is greater than maxlen
, then deque
discards the left-most items (0
in the example). Once the deque is full, appending an item on any end automatically removes the item on the other end.
Having the option to restrict the maximum number of items allows you to use deques for tracking the latest elements in a given sequence of objects or events. For example, you can track the last five transactions in a bank account, the last ten open text files in an editor, the last five pages in a browser, and more.
Note that maxlen
is available as a read-only attribute in your deques, which allows you to check if the deque is full, like in deque.maxlen == len(deque)
.
Finally, you can set maxlen
to any positive integer number representing the maximum number of items you want to store in a specific deque. If you supply a negative value to maxlen
, then you get a ValueError
.
.rotate()
The default value of n
is 1
. If you provide a negative value to n
, then the rotation is to the left:>>>
In these examples, you rotate ordinals
several times using .rotate()
with different values of n
. If you call .rotate()
without an argument, then it relies on the default value of n
and rotates the deque 1
position to the right. Calling the method with a negative n
allows you to rotate the items to the left.
.extendleft()
deque
Here are a few examples of other actions you can perform on deque
objects:>>>
Regarding other sequence methods, the following table provides a summary:
Remove all the elements from a deque.
Create a shallow copy of a deque.
Count the number times value
appears in a deque.
Return the position of value
in the deque.
Reverse the elements of the deque in place and then return None
.
Unlike lists, deques don’t include a .sort()
method to sort the sequence in place. This is because sorting a linked list would be an inefficient operation. If you ever need to sort a deque, then you can still use sorted()
.
deque
Into ActionIn the following sections, you’ll code a few small examples that will help you better understand how to use deques in your code.
To solve this problem, you can use a deque with a maxlen
of 3
:>>>
In this example, pages
keeps a list of the last three sites your application visited. Once pages
is full, adding a new site to an end of the deque automatically discards the site at the opposite end. This behavior keeps your list up to date with the last three sites you used.
Note that you can set maxlen
to any positive integer representing the number of items to store in the deque at hand. For example, if you want to keep a list of ten sites, then you can set maxlen
to 10
.
Because of that, you can safely add and remove data from both ends of a deque at the same time from separate threads without the risk of data corruption or other associated issues.
The helper function wait_seconds()
simulates that both produce()
and consume()
represent long-running operations. It returns a random wait-time value between a given range of seconds, mins
and maxs
.
The final two lines in the script create and start separate threads to execute produce()
and consume()
concurrently. If you run the script from your command line, then you’ll get an output similar to the following:
The producer thread adds numbers to the right end of the shared deque, while the consumer thread consumes numbers from the left end. To interrupt the script execution, you can press Ctrl+C on your keyboard.
tail
CommandHere’s a small Python function that emulates the core functionality of tail
:>>>
The deque in the highlighted line can only store up to the number of items you pass to lines
. This guarantees that you get the desired number of lines from the end of the input file.
As you saw before, when you create a bounded deque and initialize it with an iterable the contains more items than allowed (maxlen
), the deque
constructor discards all the leftmost items in the input. Because of that, you end up with the last maxlen
lines of the target file.
With deque
, you can code your own queues and stacks at a low level in an elegant, efficient, and Pythonic way.
In this tutorial, you learned how to:
Create and use Python’s deque
in your code
Efficiently append and pop items from both ends of a sequence with deque
Use deque
to build efficient queues and stacks in Python
Decide when to use deque
instead of list
In this tutorial, you also coded a few examples that helped you approach some common use cases of deque
in Python.
Append and pop operations on both ends of a deque
object are stable and equally efficient because deques are as a . Additionally, append and pop operations on deques are also and memory efficient. These features make deques particularly useful for creating custom stacks and queues in Python.
Stores items of any
Is a data type
Supports with the in
operator
Supports , like in a_deque[i]
Supports built-in functions that operate on sequences and iterables, such as , , , and more
Doesn’t support sorting
Supports pickling with
If you instantiate deque
without providing an iterable
as an argument, then you get an empty deque. If you provide and input iterable
, then deque
initializes the new instance with data from it. The initialization goes from left to right using .
maxlen
holds an integer that specifies the maximum length of the deque.
As mentioned previously, if you don’t supply an iterable
, then you get an empty deque. If you supply a value to , then your deque will only store up to maxlen
items.
Finally, you can also use unordered iterables, such as , to initialize your deques. In those cases, you won’t have a predefined order for the items in the final deque.
The most important difference between deque
and list
is that the former allows you to perform efficient append and pop operations on both ends of the sequence. The deque
class implements dedicated and methods that operate on the left end of the sequence directly:>>>
Just like list
, deque
also provides .append()
and methods to operate on the right end of the sequence. However, .pop()
behaves differently:>>>
As you learned earlier, deque
is implemented as a doubly linked list. So, every item in a given deque holds a reference () to the next and previous item in the sequence.
In this script, average_time()
computes the average time that executing a function (func
) a given number of times
takes. If you from your command line, then you get the following output:
Remove the first occurrence of value
, raising if the value
doesn’t exist.
Here, you first insert "c"
into letters
at position 2
. Then you remove "d"
from the deque using .remove()
. Deques also allow indexing to access items, which you use here to access "b"
at index 1
. Finally, you can use the del
to delete any existing items from a deque. Note that .remove()
lets you delete items by value, while del
removes items by index.
Even though deque
objects support indexing, they don’t support slicing. In other words, you can’t extract a from an existing deque using the , [start:stop:step]
, as you would with a regular list:>>>
So far, you’ve seen that deque
is quite similar to list
. However, while list
is based on , deque
is based on a doubly linked list.
There is a hidden cost behind deque
being implemented as a doubly linked list: accessing, inserting, and removing arbitrary items aren’t efficient operations. To perform them, the has to iterate through the deque until it gets to the desired item. So, they’re O(n) instead of O(1) operations.
This summary can help you choose the appropriate data type for the problem at hand. However, make sure to profile your code before switching from lists to deques. Both of them have their performance strengths.
As you already learned, deque
is implemented as a double-ended queue that provides a generalization of stacks and queues. In this section, you’ll learn how to use deque
for implementing your own queue at a low level in an elegant, efficient, and Pythonic way.
Note: In the Python standard library, you’ll find . This module implements multi-producer, multi-consumer queues that allow you to exchange information between multiple threads safely.
Queues are of items. You can modify queues by adding items at one end and removing items from the opposite end.
Queues manage their items in a First-In/First-Out () fashion. They work as a pipe where you push in new items at one end of the pipe and pop old items out from the other end. Adding an item to one end of a queue is known as an enqueue operation. Removing an item from the other end is called dequeue.
Here, you first create an empty deque
object to represent the queue of people arriving at the restaurant. To enqueue a person, you use , which adds individual items to the right end. To dequeue a person, you use , which removes and returns individual items on the left end of a deque.
Cool! Your queue simulation works! However, since deque
is a generalization, its doesn’t match the typical queue API. For example, instead of .enqueue()
, you have .append()
. You also have .popleft()
instead of .dequeue()
. Additionally, deque
provides several other operations that might not fit your specific needs.
The good news is that you can create custom queue classes with the functionality you need and nothing else. To do this, you can internally use a deque to store the data and provide the desired functionality in your custom queues. You can think of it as an implementation of the , in which you convert the deque’s interface into something that looks more like a queue interface.
The support the following features:
However, in the example above, the intent is to use the method’s value to gracefully display the object on the . You can make it possible to build Queue
instances from this specific string representation by accepting an initialization iterable as an argument to .__init__()
and building instances from it.
As an exercise, you can test the remaining features and implement other features, such as supporting equality tests, removing and accessing random items, and more. Go ahead and give it a try
Note that if you don’t specify a value to maxlen
, then it defaults to , and the deque can grow to an arbitrary number of items.
Another interesting feature of deques is the possibility to rotate their elements by calling on a non-empty deque. This method takes an integer n
as an argument and rotates the items n
steps to the right. In other words, it moves n
items from the right end to the left end in a circular fashion.
Like regular lists, deques provide an method, which allows you to add several items to the right end of a deque using an iterable
as an argument. Additionally, deques have a method called , which takes an iterable
as an argument and adds its items to the left end of the target deque in one go:>>>
Calling .extendleft()
with an iterable
extends the target deque to the left. Internally, .extendleft()
performs a series of individual .appendleft()
operations that process the input iterable from left to right. This ends up adding the items in reverse order to the left end of the target deque.
Since deques are mutable sequences, they implement almost all the methods and operations that are common to and . So far, you’ve learned about some of these methods and operations, such as .insert()
, indexing, membership tests, and more.
You can use the addition (+
) to concatenate two existing deques. On the other hand, the multiplication operator (*
) returns a new deque equivalent to repeating the original deque as many times as you want.
Here, .index()
can also take two optional arguments: start
and stop
. They allow you to restrict the search to those items at or after start
and before stop
. The method raises a if value
doesn’t appear in the deque at hand.
You can use deques in a fair amount of use cases, such as to implement queues, stacks, and . You can also use them to maintain an undo-redo history, enqueue incoming requests to a , keep a list of recently open files and websites, safely exchange data between multiple threads, and more.
Having a maxlen
to restrict the maximum number of items makes deque
suitable for solving several problems. For example, say you’re building an application that data from search engines and social media sites. At some point, you need to keep track of the three last sites your application requested data from.
Python’s deque
is also useful when you’re coding applications, as described by , core Python developer and creator of deque
and the collections
module:
The deque’s .append()
, .appendleft()
, .pop()
, .popleft()
, and len(d)
operations are thread-safe in CPython. ()
To try out how deque
works in a multithreaded application, fire up your favorite , create a new script called threads.py
, and add the following code to it:
Here, produce()
takes a queue
and a size
as arguments. Then it uses in a to continuously produce numbers and store them in a deque called shared_queue
. Since appending items to a deque is a thread-safe operation, you don’t need to use a to protect the shared data from other threads.
In consume()
, you call .popleft()
inside a loop to systematically retrieve and remove data from shared_queue
. You wrap the call to .popleft()
in a statement to handle those cases in which the shared queue is empty.
Note that while you defined shared_queue
in the global , you access it through local variables inside produce()
and consume()
. Accessing the global variable directly would be more problematic and definitely not a best practice.
Finally, you can play a little bit with the time interval inside produce()
and consume()
. Change the values you pass to wait_seconds()
, and watch how the program behaves when the producer is slower than the consumer and the other way around.
The final example you’ll code here emulates the , which is available on and operating systems. The command accepts a file path at the command line and prints the last ten lines of that file to the system’s standard output. You can tweak the number of lines you need tail
to print with the -n
, --lines
option.
Here, you define tail()
. The first argument, filename
, holds the path to the target file as a . The second argument, lines
, represents the number of lines you want to retrieve from the end of the target file. Note that lines
defaults to 10
to simulate the default behavior of tail
.
Note: The original idea for this example comes from the Python documentation on deque
. Check out the section on for further examples.
and are commonly used abstract data types in programming. They typically require efficient pop and append operations on either end of the underlying data structure. Python’s module provides a data type called that’s specially designed for fast and memory-efficient append and pop operations on both ends.