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.

Syntax:
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. 

ExampleEdit & Run

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)
Output:
Counter({'Python': 3, 'Java': 2, 'C++': 1, 'PHP': 1})[Finished in 0.01283463416621089s]

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

Syntax:
update(iterable=None, **kwargs)
ExampleEdit & Run

use the update()  method

from collections import Counter

c = Counter()

data = 'abcdaab'

c.update(data)

print(c)
Output:
Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1})[Finished in 0.012692966964095831s]

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.

ExampleEdit & Run

The Counter class is a subclass of the dict type

from collections import Counter

print(issubclass(Counter, dict))
Output:
True[Finished in 0.012627318035811186s]

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

Syntax:
counter['x']
ExampleEdit & Run

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'])
Output:
32[Finished in 0.012133582960814238s]

The x most common elements

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

Syntax:
most_common(n = None)

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

ExampleEdit & Run

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))
Output:
[('Python', 3), ('Java', 2), ('C++', 1)][Finished in 0.012537557864561677s]

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.

ExampleEdit & Run

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)
Output:
combined counts:Counter({'Python': 5, 'Java': 3, 'C++': 1, 'PHP': 1, 'Kotlin': 1, 'C#': 1, 'HTML': 1})subtraction:Counter({'Python': 1, 'C++': 1, 'Java': 1, 'PHP': 1})counter union:Counter({'Python': 3, 'Java': 2, 'C++': 1, 'PHP': 1, 'Kotlin': 1, 'C#': 1, 'HTML': 1})counter intersection:Counter({'Python': 2, 'Java': 1})[Finished in 0.011754679959267378s]

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.

ExampleEdit & Run

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'])
Output:
Does not existDoes not exist.[Finished in 0.009904606034979224s]

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.

Syntax:
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.

ExampleEdit & Run

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'])
Output:
PhilippinesDoes not exist[Finished in 0.011481450870633125s]

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. 

ExampleEdit & Run

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)
Output:
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])90deque([1, 2, 3, 4, 5, 6, 7, 8])[Finished in 0.011909527005627751s]

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.

ExampleEdit & Run

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)
Output:
before:  deque([2, 3, 4, 5])after:  deque([2, 3, 4, 5, 6])[Finished in 0.011855285847559571s]

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

ExampleEdit & Run

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)
Output:
before:  deque([2, 3, 4, 5])after:  deque([1, 2, 3, 4, 5])[Finished in 0.011760381981730461s]

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

ExampleEdit & Run

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)
Output:
before:  deque([2, 3, 4, 5])after:  deque([2, 3, 4, 5, 6, 7, 8, 9])[Finished in 0.011529193026944995s]

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

ExampleEdit & Run

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)
Output:
before:  deque([4, 5, 6])after:  deque([1, 2, 3, 4, 5, 6])[Finished in 0.013022519182413816s]

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. 

ExampleEdit & Run

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())
Output:
987[Finished in 0.011726390104740858s]

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

ExampleEdit & Run

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())
Output:
123[Finished in 0.011891619767993689s]

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. 

ExampleEdit & Run

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)
Output:
before:  deque([1, 2, 3, 4, 5, 6, 7, 8, 9])after:  deque([7, 8, 9, 1, 2, 3, 4, 5, 6])[Finished in 0.011968018021434546s]

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

ExampleEdit & Run
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)
Output:
before:  deque([1, 2, 3, 4, 5, 6, 7, 8, 9])after:  deque([4, 5, 6, 7, 8, 9, 1, 2, 3])[Finished in 0.01211729901842773s]

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.

Syntax:
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.
ExampleEdit & Run
from collections import namedtuple

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

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

print(p)
print(p.x)
print(p.y)
Output:
Point(x=2, y=3)23[Finished in 0.012687253067269921s]

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

ExampleEdit & Run
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)
Output:
Person(name='John', age='30', spouse='Mary')name:  Johnage:  30spouse:  Mary[Finished in 0.011979734990745783s]

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.

ExampleEdit & Run
from collections import OrderedDict

od = OrderedDict()
od[1] = 'One'
od[2] = 'Two'
od[3] = 'Three'
for k, v in od.items():
    print(k, v)
Output:
1 One2 Two3 Three[Finished in 0.012202339945361018s]

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

ExampleEdit & Run
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)
Output:
False[Finished in 0.011427509132772684s]