avatarYash Prakash

Summarize

9 Special Data Structures in Python

A Detailed Guide to Python Collections: Go beyond the fundamentals by learning the data structures along with the accompanying code.

Photo by Kinga Cichewicz on Unsplash

We are all familiar with the regular data structures in Python — the lists, dictionaries, tuples and sets. We may even be quite well versed in it since they do come up quite often in our day to day Python programming.

In this article, we’ll go into learning some of the special data structures, those which are based on or derived from these basic data structures.

If you already know them, this little tutorial might be just a good recall for you as well.

Let’s get started! 👇

Python has the collections module which has all these special data structures for us to use in our daily tasks. They come in handy in a variety of ways, and if you’re a competitive programmer or a data scientist or a machine learning engineer, you should be familiar with at least a few of these classes too.

There are nine specialised ‘containers’, as they call them, in the collections module. They include:

  1. deque
  2. OrderedDict
  3. namedtuple()
  4. Counter
  5. ChainMap
  6. defaultdict
  7. UserString
  8. UserList, and finally,
  9. UserDict

First, go ahead and import the module with:

from collections import *

Now, let us look into their functionalities one by one!

The deque container — working with queues and stacks in one!

Compared to the normal lists, deque offers functions to append(insert item) and remove(delete item) from both sides of the list. It is normally regarded as a doubly ended queue for the same reason as well.

# init an example
queue = duque()
queue.append(3)
queue.append('Mine')
deque example!
queue.append('True')
queue[2]

Major functions as we can gather from the above example, also work similarly as the regular lists do.

The time complexity for append and pop operations are, however, optimised to O(1) instead of the O(n) in lists.

The special functions in deque can be demonstrated with the following code snippet:

# Looking at some special functions which sets it apart
FIFO_element = queue.popleft()
queue.appendleft('3')

As it must be clear from the above code snippet, we can pop the item in a First In First Out order, as we do in a queue as well.

Isn’t this convenient?

The OrderedDict container — for preserving our order of insertion!

This one is a subclass of our good old dictionary class, but what’s better is that it provides us with the functionality to preserve the order in which we insert our elements into the dictionary.

Let’s get better at understanding it with an example.

# init an example to demonstrate the difference between the two!
print("This is a Dict:")  
d = {}  
d['First'] = 1
d['Dos'] = 2
d['Dritte'] = 3
d['Quatre'] = 4
    
for key, value in d.items():  
    print(key, value)  
    
print("\nThis is an Ordered Dict:")  
od = OrderedDict()  
od['First'] = 1
od['Quatre'] = 4
od['Dos'] = 2
od['Dritte'] = 3

    
for key, value in od.items():  
    print(key, value)

And here are the results:

ordereddict demonstration!

This must give you an idea of what we were talking about before.

What’s even better is that it OrderedDict retains all the functions of a regular dict as well!

But that’s not it, we have a special function too in here, of course.

Here it is:

popitem(last=True)

This function is the one that gives us the power to remove an element in either of two ways:

  1. FIFO manner — by setting last = False
  2. LIFO manner — keeping last as True.

So this happens:

od.popitem()   #LIFO pop
od.popitem(last = False) #FIFO pop
results for lifo and fifo pop operation

Let’s move on to the next one!

The namedtuple() class — for providing custom names for our tuples!

As the official documentation describes it:

The new subclass is used to create tuple-like objects that have fields accessible by attribute lookup as well as being indexable and iterable.

So essentially, a namedtuple class can be used to return a tuple object with names for each position.

This is the basic concept to understand here — we can have a name associated with each tuple, rest of the functionality for the tuple is the same.

Another special function to take note of here is the _make(list) function. This one returns a list as our namedtuple. Like this:

# init an example to show basic functionality 
Tasks = namedtuple('Tasks',  ['name',  'language',  'when'])
first_task = Tasks('Finish the app.py file', 'python', 'tomorrow')
first_task.name

Also:

# to get a list as a namedtuple, we can do this:
l = ['Finish presentation', 'Java', 'Sunday']

second_task = Tasks._make(l)
second_task

The Counter class — for speedy item counting!

For getting a count of every element in a dictionary or a list, we can have the Counter class do it for us!

Here is an example:

mylist = [1,2, 56, 23, 2, 67, 23, 78]
count = Counter(my list)
counter class

As you can see, it returns the count of elements as a dictionary.

That is it for our Counter class!

Go ahead and save at least ten lines of code through this class (and a for loop for counting elements that you won’t need now) :)

ChainMap container — to store a collection of dictionaries as one!

The main functionality is essentially this — it encapsulates many dictionaries into a single unit and returns a list of dictionaries.

Look at this example:

''' Demonstrating a ChainMap! '''
d1 = {'Bands': ['Coldplay', 'Maroon5', 'One Direction']}
d2 = {'Some other bands': ['Metallica', 'AC/DC']}
c = ChainMap(d1, d2)

Then, print them out simply like so:

c.keys()
c.values()

It is as simple as that! And it can still be called in the same manner as our normal dictionaries. Adding another dictionary is also very simple, just use the new_child function!

The defaultdict class — a default, error-free dictionary at our disposal!

It is a subclass of dict class and returns a new dictionary-like object, and for the keys not present in the dictionary, it provides some default value to never raise a KeyError, as you can see from the example.

# defining the defaultdict  
d = defaultdict(int)  
     
# add values
d['One'] = 2
d['Two'] = 1
defaultdict

It is the simplest one to understand in our whole collections module.

Custom containers with UserDict, UserList and UserString!

These three classes essentially allow you to use the built-in dictionaries, lists or strings and infuse them with any custom functions, as you’d like according to your use case. UserList acts as a wrapper around list objects, UserDict around dict objects and UserString around the string objects.

Let us look at an example. Here, we define a custom list with UserList in which we add a new method of our own to make and return a dictionary with alternate elements from the list.

Here is what I’m talking about:

'''A custom List with UserList '''

class MyCustomList(UserList):  
    # a custom function to return a dictionary with alternating elements
    def convert_to_dict(self):  
        d = defaultdict(int)
        for i in range(0, len(self)-1, 2):
            d[l[i]] = l[i+1] 
        return d
            
# define an instance
l = MyCustomList(['One', 1, 'Two', 2, 'Three', 3])  
print(l.convert_to_dict())
Custom function for lists!

Ha! We’ve successfully added a custom feature to our lists!

The implementation of UserDict and UserString is quite similar, so now that you have the idea, please go ahead and play with them on your own. :)

Thank you for reading and I hope you find this article helpful!

👉 I will be coming back with more future tutorials on Python as well. I tend to write one on Programming and Data Science every week. So, stay with me for those.

Join Medium if you want to read more from me. It helps support my writing and means the world to me!

Happy learning! :)

Here is my GitHub repository with the full code, which also contains all of the resources from my articles:

More content at plainenglish.io

Python
Programming
Coding
Technology
Data Science
Recommended from ReadMedium