Python comes with some builtin container data types such as lists, tuplessets,  dicts, etc. The collections module in the standard library extends the functionality of these types by providing specialized container data structures. The data structures are as summarized below:

structure description
Counter A container that stores elements as dictionary keys, and each element's count as the value.
deque An implementation of a double-ended queue in which elements can be inserted and retrieved from either ends.
namedtuple A structure similar to tuples but in which elements can be accessed with a name not just their numeric indices.
OrderedDict Similar to standard dictionaries but elements are ordered by their order of insertion.  
defaultdict A type of dictionary that allows the default value for non-existent keys to be set in advance at instantiation.
ChainMap A structure that links multiple mappings/dictionaries into a single unit.
UserDict Provides an interface for subclassing standard dict type.
UserList Provides an interface for subclassing standard list type.
UserString Provides an interface for subclassing standard str type..

These containers provides alternative or extension for standard data types, they have specialized features and functionality making them more suitable in some situations.

Counter data structure

Counter objects of the collections.Counter datatype tracks the number of occurrences of each item within a collection or sequence.

Counter(iterable = None, **kwargs)

Counter objects keeps a mapping of elements to the number of occurrences of a particular element in the given iterable object. 

initializing Counter objects

#import the collections module
import collections

#An iterable object
L = ['Python', 'C++', 'Python', 'Java', 'PHP', 'Java', 'Python']

c = collections.Counter(L)

print(c)

We can use the update() method of Counter objects to change the iterable after instantiation. 

update(iterable=None, **kwargs)

use the update()  method

from collections import Counter

c = Counter()

data = 'abcdaab'

c.update(data)

print(c)

Accessing the counts

Once the Counter object have been created, we can access the counts for a given element similarly to how we would access an element from a dictionary. In fact, the Counter class is actually a subclass of the builtin dict class.

The Counter class is a subclass of the dict type

from collections import Counter

print(issubclass(Counter, dict))

Thus to access the count of an element 'x' in a Counter object with a name  'counter', we would use the following syntax.

counter['x']

Access the count of an element

#import the collections module
import collections

#An iterable object
L = ['Python', 'C++', 'Python', 'Java', 'PHP', 'Java', 'Python']

c = collections.Counter(L)

print(c['Python'])
print(c['Java'])

The x most common elements

The Counter class defines the most_common method which gets x most common elements.

most_common(n = None)

If n is not given, the method returns the counts of all elements

Get x most common elements

#import the collections module
import collections

#An iterable object
L = ['Python', 'C++', 'Python', 'Java', 'PHP', 'Java', 'Python']

c = collections.Counter(L)

print(c.most_common(3))

Counter arithmetic

Counter objects supports arithmetic and set operations. This makes it possible to easily do calculations on the data in the Counter object. It is possible to add two counters together, subtract one counter from another, find the union of multiple counters, and perform various other operations.

perform operations on Counter objects

#import the collections module
import collections

#An iterable object
L = ['Python', 'C++', 'Python', 'Java', 'PHP', 'Java', 'Python']

T = ('Java', 'Kotlin', 'C#', 'Python', 'HTML', 'Python')

c1 = collections.Counter(L)
c2 = collections.Counter(T)

print('combined counts:')
print(c1 + c2)

print('\nsubtraction:')
print(c1 - c2)

print('\ncounter union:')
print(c1|c2 )

print('\ncounter intersection:')
print(c1 & c2)

defaultdict data structure

The standard dict type allow setting default values in cases when the value being retrieved does not exist in the dictionary. This is primarily achieved  through either the get() or the setdefault() methods.

Setting default values for the standard dictionary

D = {'America': 'Washington', 'Rwanda': 'Kigali', 'Japan': 'Tokyo', 'Finland': 'Helsinki',  'Canada': 'Ottawa'}

#set a default value during retrieve
print(D.get('China', 'Does not exist'))

#set the default for a particular key
D.setdefault('India', 'Does not exist.')
print(D['India'])

The defaultdict, works similarly to the standard dicts but it allows us to set the default value for the all keys in advance during instantiation.

default_dict(default_factory = None, *args)

The default_factory argument is literally a function that gets called without any argument to produce the default value that will be returned if the requested item does not exist.

use the defaultdict data type 

from collections import defaultdict

def factory():
    return 'Does not exist'

D = defaultdict(factory, Kigali = 'Rwanda', Ottawa = 'Canada', Manilla = 'Philippines')

print(D['Manilla'])
print(D['Washington'])

Deque data structure

A queue data structure is a type of a container in which elements are stored and accessed in a "first in, first out" (FIFO) manner. In double-ended queue(deque) elements can be added or removed from both ends, allowing for "first in, first out" (FIFO) and "last in, first out" (LIFO) access. This makes it a useful data structure for applications such as stacks, queues, and double-ended priority queues.

The deque class in the collections modules  provides the functionality for manipulating double-ended queues. Deque objects support efficient insertion and removal of the entities from both ends of the queue. 

Instantiating deque objects

We can simply instantiate the deque objects without any argument to create an empty deque. We can also pass an iterable object during instantiation to create a deque with the elements present in the iterable. 

Create a deque from a list

from collections import deque

L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

d  = deque(L)

print(d)

#remove an element from the back
print(d.pop())

#remove an element from the front
print(d.popleft())

print(d)

Adding elements to the deque

We can add elements to both ends of the deque using various methods.

adding single element

The append() method  is used to add an element at the back of the deque.

Add an element at the back of the deque

from collections import deque

L = [2, 3, 4, 5]

d  = deque(L)
print('before: ', d)

d.append(6)

print('after: ', d)

To add an element at the front, we use the appendleft() method.

Add an element at the front of the deque

from collections import deque

L = [2, 3, 4, 5]

d  = deque(L)
print('before: ', d)

d.appendleft(1)

print('after: ', d)

Add multiple elements at once

The deque class also contain methods for extending deque objects from both ends. The extend() methods appends iteratively all the elements of an iterable given as an argument, at the end of the deque

extend a deque at back

from collections import deque

L = [2, 3, 4, 5]

d  = deque(L)

print('before: ', d)
extra = [6, 7, 8, 9]

d.extend(extra)

print('after: ', d)

Similarly, the extendleft() method extends the deque object from the front.

extend a deque at front

from collections import deque

L = [4, 5, 6]

d  = deque(L)

print('before: ', d)

extra = [3, 2, 1]

d.extendleft(extra)

print('after: ', d)

Retrieving items from the deque

As with insertions, elements in a deque can be retrieved from either ends. The pop() method removes the item at the back  of the deque and returns it. 

Remove the item at  the back of the deque

from collections import deque

L = [1,2, 3, 4, 5, 6, 7, 8, 9]

d  = deque(L)

#remove the last item
print(d.pop())
print(d.pop())
print(d.pop())

The popleft() method removes the leftmost item and returns. 

Remove the leftmost item from deque

from collections import deque

L = [1,2, 3, 4, 5, 6, 7, 8, 9]

d  = deque(L)

#remove the last item
print(d.popleft())
print(d.popleft())
print(d.popleft())

deques are thread safe, this makes it  even posssible to remove items from both ends simultaneously from different threads.

rotating deque elements

Rotating is the process of moving items from one end of a deque to the other. This is useful for situations where elements need to be cycled through, such as when implementing a priority queue.

Rotating the deque to the right moves elements from the back to the front end of the deque, this is achieved using the rotate() method with a positive value as the argument. 

Rotate a deque to the right

from collections import deque

L = [1,2, 3, 4, 5, 6, 7, 8, 9]

d  = deque(L)

print('before: ', d)

#Rotate the elements
d.rotate(3)

print('after: ', d)

To rotate the deque to the left, we use the rotate() method  with a negative value.

from collections import deque

L = [1,2, 3, 4, 5, 6, 7, 8, 9]

d  = deque(L)

print('before: ', d)

#Rotate the elements
d.rotate(-3)

print('after: ', d)

namedtuple  data structure

The namedtuple objects are tuple-like data structures but  with named fields similar to dictionaries. This makes it possible to  access the items by name instead of just by indices.

When instantiating namedtuple types  we provide a type name and the values for each field.

namedtple(name, fields, rename = False, defaults = None, module = None)
name  The name of the tuple type
fields An iterable such as a list containg the names of the relevant fields.
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])

p = Point(x = 2, y = 3)

print(p)
print(p.x)
print(p.y)

Note that just like the standard tuples, namedtuples  are Immutable and we cannot change the elements after creation.

from collections import namedtuple

Person = namedtuple('Person', ['name', 'age', 'spouse'])

p = Person('John', '30', 'Mary')

print(p)
print('name: ', p[0])
print('age: ', p.age)
print('spouse: ', p.spouse)

OrderedDict data structure

In standard dict type, the order of elements is not preserved.  An OrderedDict objects simply works similarly to the regular dicts, except that  OrderedDict objects remember the order in which elements are inserted. This can be useful for tracking the order in which key-value pairs are added, as well as for outputting dictionary contents in a specific order.

from collections import OrderedDict

od = OrderedDict()
od[1] = 'One'
od[2] = 'Two'
od[3] = 'Three'
for k, v in od.items():
    print(k, v)

Note that the order of elements will also be taken into consinderation when performing comparison operations e.g equality  on OrderDicts.

from collections import OrderedDict

#regular dict
d1 = OrderedDict()

d1['a'] = 'A'
d1['b'] = 'B'
d1['c'] = 'C'

d2 = OrderedDict()
d2['a'] = 'C'
d2['b'] = 'B'
d2['a'] = 'A'

print(d1 == d2)