Python+ 101: Most useful data structures and algorithms | by Neerav Kaushal | Towards Data Science

A new way of pythoning with less known python features
Python is undoubtedly one of the most widely used programming languages of this century that has given a whole new perspective to programming in terms of ease of use and intuitiveness. Besides having a rich set of the most common data structures, it provides a number of standard libraries that can greatly reduce the burden of developing codes from scratch for a variety of problems pertaining to different fields. In this tutorial, I will focus on some of the most common problems or should I say, encounters, and their solutions using data structures and algorithms of standard python modules. So let’s dive into the world of “Python+” which is all about adding something more into what we know of Python.
1. N largest or smallest elements in a list, tuple, set or numpy array
In many scenarios, we need to extract the largest or smallest elements in a collection. The nlargest and nsmallest methods of the heapq module of python can be used to obtain N largest and smallest elements in a list. nlargest(n,iterable) and nsmallest(n,iterable) where n is the number of largest or smallest elements that need to be obtained and iterable can be a list, a tuple, a set or a numpy array can be used as follows.
import numpy as np
import heapq

mylist = list(range(1,1000))
mytuple = tuple(range(-500,500,2))
myarray = np.arange(-1000,1000,1)

print( heapq.nlargest(4,mylist)  )
print( heapq.nlargest(4,mytuple) )
print( heapq.nlargest(4,myarray) )

[999, 998, 997, 996]
[498, 496, 494, 492]
[999, 998, 997, 996]

print( heapq.nsmallest(5,mylist)  )
print( heapq.nsmallest(5,mytuple) )
print( heapq.nsmallest(5,myarray) )

[1, 2, 3, 4, 5]
[-500, -498, -496, -494, -492]
[-1000, -999, -998, -997, -996]

These methods are faster and more efficient in case more than one largest or smallest elements in a collection is needed. If the single largest or smallest element is needed, the min and max functions in python are more efficient. Note that the nlargest and nsmallest functions always return a list irrespective of the type of the collection.
2. Most common element and number of occurrences of an element in a collection
Sometimes, we need to find N most common elements in a collection. The Counter class of the collections library is a simple way of extracting the most common elements in a list or numpy array along with the number of their occurrences, with itsmost_common method.
import numpy as np
from collections import Counter

myarray = np.random.randint(0,10,30)
print(myarray)
myarray = Counter(myarray)

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

myarray.most_common(2)

[(1, 6), (0, 3)]
The most_common method takes the number of most common elements we want as an argument and returns a list of tuples whose first element denotes the most common element of the array and the second element is the number of that element’s occurences, sorted according to decreasing occurences. In the snippet shown in Figure 2, 1 in myarray occurs 6 times and 0 occurs 3 times.
Also, the number of occurences of a specific element can be obtained by indexing. The element 5, for example, occurs 3 times in myarray which can be found by simply using myarray[5].
Counter is a very useful class (subclass to be more precise) for counting hashable items. The elements of the array or list are stored as dictionary keys with their counts as corresponding dictionary values.
3. Tabular Categorization: Bisecting an array
Sometimes, it is required to classify/categorize data based on some breakpoints. The bisect method of the bisect module is an excellent tool to achieve this categorization without much hassle. Consider the scores of 10 students in an exam out of 100. We need to place them in 4 classes based on whether their marks are less than 25, between 25 and 50, between 50 and 75 or above 75. This can be done as follows
import numpy as np
from bisect import bisect

breakpoints = [25,50,75]
classes = ['Below 25', '25 to 50', '50 to 75', '75 and above']
scores  = np.random.randint(0,100,10)
scores

array([83, 71, 86, 98, 61, 39, 72, 96, 84, 24])
[classes[ bisect(breakpoints, score) ] for score in scores]

['75 and above',
 '50 to 75',
 '75 and above',
 '75 and above',
 '50 to 75',
 '25 to 50',
 '50 to 75',
 '75 and above',
 '75 and above',
 'Below 25']
Each element of the array scores is put into their respective class inclasses based on the breakpoints.
4. Permutations and Combinations
In my research involving terabytes of data, I encounter the need of permutations and combinations most frequently in file operations. The itertools library provides some decent methods to calculate the permutations and combinations (with and without replacements) of data. permutations(iterable, r) provides the number of r-length permutations of iterable which can be a list or an array. Similarly, combinations() and combinations_with_replacement() returns the combinations with and without replacement of the iterable respectively. The latter allows the individual elements to have successive repeats. The arguments for all the three functions are the same.
from itertools import permutations, combinations, combinations_with_replacement

list( permutations('ABCD',2) )

[('A', 'B'),
 ('A', 'C'),
 ('A', 'D'),
 ('B', 'A'),
 ('B', 'C'),
 ('B', 'D'),
 ('C', 'A'),
 ('C', 'B'),
 ('C', 'D'),
 ('D', 'A'),
 ('D', 'B'),
 ('D', 'C')]
list( combinations('ABCDE',2) )

[('A', 'B'),
 ('A', 'C'),
 ('A', 'D'),
 ('A', 'E'),
 ('B', 'C'),
 ('B', 'D'),
 ('B', 'E'),
 ('C', 'D'),
 ('C', 'E'),
 ('D', 'E')]
list( combinations_with_replacement('ABCDE',2) )

[('A', 'A'),
 ('A', 'B'),
 ('A', 'C'),
 ('A', 'D'),
 ('A', 'E'),
 ('B', 'B'),
 ('B', 'C'),
 ('B', 'D'),
 ('B', 'E'),
 ('C', 'C'),
 ('C', 'D'),
 ('C', 'E'),
 ('D', 'D'),
 ('D', 'E'),
 ('E', 'E')]
Thanks for reading!
I hope it helps. Any comment or criticism is most welcome.