Skip to content

Instantly share code, notes, and snippets.

@adityajn105
Last active April 26, 2022 03:02
Show Gist options
  • Save adityajn105/67993e8ab7ad429d7c2c109891b2390c to your computer and use it in GitHub Desktop.
Save adityajn105/67993e8ab7ad429d7c2c109891b2390c to your computer and use it in GitHub Desktop.
Useful programming templates implemented in python 3

General Snippets

1. Prime Factors of a number

  • Step1 : divide number by 2 and add 2 as factor till possible
  • Step2 : Start k from 3 with step of 2 till sqrt(number), divide number by k and add k as factor till possible
  • Step3 : Check if number remaining is greater than 1, add that number as another factor.
def getPrimeFactors(n):
    factors = []
    while n%2==0:
        factors.append(2)
        n = n//2
    k=3
    while k*k <= n:
        if n%k==0:
            n= n//k
            factors.append(k)
        else:
            k+=2
	    
    if n>1:
        factors.append(n)
    return factors
    
>> getPrimeFactors(36)
>> [2, 2, 3, 3]

>> getPrimeFactors(999999901)
>> [61, 463, 35407]

2. Total number or distinct factors/divisors of a given number

>> getPrimeFactors(4410)
>> [ 2, 3, 3, 5, 7, 7 ] 
  • Now, 4410 = 2^a * 3^b * 5^c * 7^d
  • a could be (0,1), b could be (0,1,2) and so on.
  • Total Factors will be 232*3 = 36
from collections import defaultdict
def totalFactors(n):
    count = defaultdict(int)
    for a in getPrimeFactors(n):
        count[a]+=1
    total = 1
    for cnt in count.values():
        total*=(cnt+1)
    return total
>> totalFactors(100)
>> 9

def factors(n):
    fact = []
    for i in range(1, int(n**0.5)+1):
        if n%i == 0:
            if n//i == i:
                fact.append(i)
            else:
                fact.append(i)
                fact.append(n//i)
    return fact
>> factors(100)
>> [1, 2, 4, 5, 10, 20, 25, 50, 100]

2. GCD(Greatest Common Divisor/Factor) of n numbers

  • Euclidean Method
  • GCD(a,b,c) = GCD(a,gcd(b,c))
  • (100,24,6) -> (24,4,6) -> (4,0,6) -> (4,6) -> (6,4) -> (4,2) -> (2,0) -> 2
from functools import reduce
def GCD(*args):
  gcd2 = lambda a,b: a if b==0 else gcd2(b,a%b)
  return reduce( gcd2 , args )
  
>> a = [9,6,18]
>> GCM(*a)
3

3. LCM(Lowest Common Multiple) of n numbers

  • Euclidean Method
  • LCM(a,b) = a*b/GCD(a,b)
  • LCM(a,b,c) = LCM(a,LCM(a,b))
from functools import reduce
def LCM(*args):
  lcm2 = lambda a,b : int(a*b/GCD(a,b))
  return reduce( lcm2 , args )

>> a = [3,9,6]
>> LCM(*a)
18

4. Extended Euclidean Algorithm

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    gcd = b
    return gcd, x, y

>> egcd(56,15)
>> (1, -4, 15)

5. Find integral solution of 2 variable linear equation.

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
    gcd = b
    return gcd, x, y

def find_any_solution(a,b,c): #ax+by = c
    g,x0,y0 = egcd( abs(a), abs(b) )
    
    if c%g != 0: #no integer solution
        return False
    
    x0 *= c//g
    y0 *= c//g
    if a<0: x0 = -1*x0
    if b<0: y0 = -1*y0
    return x0,y0,g

def find_all_solutions(a,b,c):
    soln = find_any_solution(a,b,c)
    if not soln:
        return False
    x0,y0,g = soln
    k=0
    while True:
        yield x0+k*(b/g), y0-k*(a/g)
        k+=1

5. Prime Numbers between 0 and n (Using Sieve of Eratosthenes)

  • Sieve of Eratosthenes is an ancient method for finding prime numbers
  • starting from lowest element(curr) in sieve remove element if item%curr==0
def primes(end,start=0):
    is_prime = [True]*(end+1)
    is_prime[0] = is_prime[1] = False
    prime_list = []
    for i in range(2,end+1):
        if is_prime[i]: 
            prime_list.append(i)
            for j in range( i*i, end+1, i):
                is_prime[j] = False
    return prime_list

6. Check if number is Prime

  • Check for every number b/w (2,sqrt(n)), if number is evenly divisible by any then it is not prime
def isPrime(n):
	if n<=2: return True if n==2 else False
	if n%2==0: return False
	for i in range(3,int(n**0.5)+1,2):
		if n%i==0: return False
	return True
	
>> isPrime(21)
False
>> isPrime(37)
True

6. Binary Search

  • Simple Binary search to avoid coding this during time of competitive coding.
def binSearch(arr,value, key= lambda x : x):
    l,h = 0,len(arr)-1
    while l<=h:
        x = (h+l)//2
        k = key(arr[x])
        if value < k:
            h = x-1
        elif value > k:
            l = x+1
        else:
            return x
    return -1
  • We can also use bisect_left or bisect_right which has similar functionality as upper_bound and lower_bound in c++.
from bisect import bisect_left, bisect_right
>> arr = [1,2,5,5,9,10]
>> bisect_left( arr, 5 )
2
>> bisect_right( arr, 5 )
4

def bisect_left(array, value):
    l,h = 0, len(array)-1
    if h==-1: return 0
    first_pos = float('inf')
    while l<=h:
        x = (l+h)//2
        if array[x] < value: l = first_pos = x+1
        else:
            if x < first_pos: first_pos = x
            h = x-1
    return first_pos

def bisect_right(array,value):
    l,h = 0, len(array)-1
    last_pos = 0
    while l<=h:
        x = (l+h)//2
        if array[x] > value: 
            if x < last_pos: last_pos = x
            h = x-1
        else:
            last_pos = l = x+1
    return last_pos

7. Intesection of two rectangles

		 _______________ (x4,y4) 
		|               |
	  ______|____B(x2,y2)   |
	 |	|    |          |
	 |______|____|          |
      (x1,y1) 	| A             |
		|_______________|
	 (x3,y3)
  • A = max(x1,x3), max(y1,y3)
  • B = min(x2,x4), min(y2,y4)
  • if B.x < A.x or B.y < A.y then no intersection
def intersection(x1,y1,x2,y2,x3,y3,x4,y4): 
    #x1, y1, x2, y2 -> bottom left and top right of first rect, similar for second
    a,b,c,d = max(x1,x3), max(y1,y3), min(x2,x4), min(y2,y4)
    if b > d or a > c: return -1
    else: return a,b,c,d

6. Topological Sort

  • We Will maintain 2 data structures a) for each node w, the number of incoming edges w has from active nodes (nodes that not have been deleted). b) the set S of all active node in G that has no incoming edges from other active nodes
  1. At start all nodes are active, do a linear pass over all edges and initialize above two data structures.
  2. For each iteration select a node v from S and delete it and print it.
  3. After deleting v, subtract 1 from number of incoming edges of node u that has an edge from v.
  4. Go to step 2
#n is number of nodes
graph = defaultdict(list)
incoming_edges = [0]*n
for b,a in edges:
    graph[b].append(a)
    incoming_edges[a]+=1

active_nodes = [ i for i in range(n) if incoming_edges[i]==0 ]
order = []
while len(active_nodes) != 0:
    curr = active_nodes.pop()
    order.append(curr)
    for child in graph[curr]:
	incoming_edges[child] -= 1
	if incoming_edges[child] == 0:
	    active_nodes.append(child)

if len(order) != n:
    return [] #no topological order, cycle is present
else:
    return order

7. Minimum of every rolling window of some window size

  • For window of size "window", find out minimum of it.
  • Use queue, if first element in queue is "window" length away then pop first element
  • if last element in queue is greater than current element then pop last element
from queue import deque
def getRollingMin(array, window):
    n = len(array)
    q = deque([])
    result = []
    for i in range(n):
        val = array[i]
        while len(q) > 0 and q[0][0] <= i-window: q.popleft()
        while len(q) > 0 and q[-1][1] >= val: q.pop()
        q.append( (i, val) )
        if i >= window-1:
            result.append( q[0][1] )
    return result

array = [856, 276, 408, 899, 928, 580, 186, 735, 725, 204]
x = 4
getRollingMin(space, x)

8. Longest Common Subsequence

  • Dynamic Programming
  • LCSS[i][j] = 1 + LCSS[i+1][j+1] if S1[i] == S2[j]
  • LCSS[i][j] = 0 + max( LCSS[i+1][j], LCSS[i][j+1] ) otherwise
  • Hint: Use loop backwards to calculate these values (to avoid overflow error)
def length(i,j):
        if grid[i][j]!=-1: 
            return grid[i][j]
        elif s1[i] != s2[j]:
            if i+1 >= w or j+1 >= w:
                if i+1 < w: grid[i][j] = length(i+1,j)
                if j+1 < w: grid[i][j] = length(i,j+1)
                grid[i][j] = max(0, grid[i][j])
            else:
                grid[i][j] = 0+max( length(i+1,j), length(i,j+1) )
        else:
	    if i+1 >= w or j+1 >= w:
                grid[i][j] = 1
            else:
                grid[i][j] = 1 + length(i+1,j+1)
        return grid[i][j]

Avoid Time Complexity(Mathematical)

1. Modulo Multiplicative Inverse

  • In some problems you are asked to give answer in terms of modulo of some prime(M) (say, 1e9+7)
  • This is done to reduce the size of answer
  • For example:
    1. (a+b)%M = ( a%M + b%M ) % M
    2. (a-b)%M = ( a%M - b%M ) % M
    3. (a.b)%M = ( a%M . b%M ) % M
    4. BUT, (a/b)%M != ( a%M / b%M ) % M, Here we will use multiplicative inverse of b
  • (a/b) % M = ( a . b^-1 ) % M = ( a%M . (b^-1)%M ) % M, How to find multiplicative inverse of b
  • a.a^-1 % M = 1 ---eq(1)
  • **By Fermet's Little Theoram, if M is prime, a^(M-1) % M = 1 **
    1. Ex, a=2,m=5 we have 2^(4) % 5 = 16%5 = 1
  • a.a^(M-2) % M == 1 By Fermet's Theoram
  • a^-1 = a^(M-2) ---using eq(1)
  • Thus multiplicate inverse of a is a^(M-2)
def moduloMulInv(a,M):
    return (a**(M-2))%M

2. Exponential Squaring

  • While taking a^n where n is a large number, it is quite time consuming so we use exponential squaring
  • Observation
  • Visit link for more info
  • Instead Use pow(a,b,z=mod), it calculates (a**b)%z using exponential squaring
def expSqr(a,n):
    if n<0 : return expSqr( 1/a, -1*n )
    if n==1: return a
    if n==0: return 1
    if n%2==0:
        return expSqr( a*a, n//2 )
    else:
        return (a%n) * (expSqr( a*a, (n-1)//2 ))

3. Use lookups

  • Instead of seaching a list everytime to see if item is visited which is O(n), use lookups,
  • Create lookup as lookup = [False]*n, and see if item is visited, this is of TC : O(1)

4. Use For Loops instead of While Loops whenever possible in python

  • Also use this function to keep track of time taken by each function anywhere, it outputs in stderr so will not mess up stdout
def giveruntime(func):
    import time,sys
    def wrap(*args,**kwargs):
        s = time.time()
        ans = func(*args,**kwargs)
        print( "Time taken", time.time()-s, file=sys.stderr )
        return ans
    return wrap

@giveruntime
def forLoop(n=1000000):
    for i in range(n): pass

@giveruntime
def whileLoop(n=1000000):
    i = 0
    while i<n: i+=1

>> forLoop(999999999)
Time taken 41.75519561767578

>> whileLoop(999999999)
Time taken 104.35621953010559

QUICK SORTING ALGORITHMS

1. Counting Sort - Complexity O(n) - Not Inplace - Not Stable

  • Suitable where we have to sort limited range of items. Not good for large range of item values.
  • Initialize an array with size equal to range of total items as all zeros.
  • Increase the count at each index with index equal to value of item
def CountingSort(values):
	arr = [ 0 for _ in range(max(values)) ]
	for a in values: arr[a]+=1
	sortedval = []
	for i in range(len(arr)):
    		sortedval += [i]*arr[i]
	return sortedval

2. Randomized Quick Sort - Complexity O(nlogn) - Not stable -

  • Very practical, Inplace, No extra memory. Hower unstable
  • On average runs in O(nlogn) time complexitiy.
  • Key idea is to select a pivot. This pivot divides the array in 2 halves, one with numbers smaller than pivot and other numbers greater than pivot. After this pivot is at the correct position.
import random
def quickSort( arr, low=0, high=None, pivot="random" ):
    """
    pivot : 
        first, last, middle, random
    """
    if high == None: high = len(arr)-1
    if high-low >= 1:
        pvt = None
        if pivot == 'first': pvt = low
        elif pivot == 'last': pvt = high
        elif pivot == 'middle': pvt = low + (high-low)//2
        else: pvt = random.randint( low, high )

        partn, comparisions = partition( arr, low, high, pvt )
        comparisions += quickSort( arr, low, partn-1, pivot=pivot )
        comparisions += quickSort( arr, partn+1, high, pivot=pivot )
        return comparisions
    else:
        return 0

def partition( arr, low, high, pvt ):

    arr[pvt], arr[high], pvt = arr[high], arr[pvt], high #swap high with choosen pivot

    pivot = arr[pvt] #pivot element
    idx = low #pointer to last switched element
    ptr = low #pointer to current element to compare
    
    comparisions = 0
    #routine with high as pivot
    while ptr <= high-1:
        comparisions+=1
        if arr[ptr] < pivot:
            arr[ptr], arr[idx] = arr[idx], arr[ptr]
            idx+=1
        ptr+=1
    
    arr[idx], arr[pvt] = pivot, arr[idx]

    return idx, comparisions

Cool Data Structures

1. Heaps

  • Application : Algorithm doing repeated minimum operations, heap sort, priority queue (event manager), median maintance,
  • Running Time : O(logn) - Insertion, O(logn) - extract min, O(nlogn) - heapify, O(logn) - deletion
  • Improvement : Implement decreaseKey in O(logn), maintain a hash table with all key pointing to their position.
class Heap():
    def __init__(self, arr=[], key=lambda x: x, value=lambda x: x):
        self.arr = arr
        self.key = key
        self.value = value
        self.hashtable = dict()
        for i in range(len(self)):
            self.hashtable[ self.__getValue(i) ] = i
        self.heapify()
        
    def __repr__(self): return f"{self.arr}"
    def __len__(self): return len(self.arr)
    def __getitem__(self, i): return self.key( self.arr[i] )
    
    def peek(self): return self.arr[0]
    def isEmpty(self): return len(self)==0
    
    def __swap_hash_indices(self, i,j):
        self.hashtable[ self.__getValue(i) ], self.hashtable[ self.__getValue(j) ] = \
            self.hashtable[ self.__getValue(j) ], self.hashtable[ self.__getValue(i) ]

    def __getValue(self, i):
        return self.value(self.arr[i])

    def __iter__(self):
        while len(self)>0:
            yield self.extractMin()

    def getItem(self, val):
        return self.arr[self.hashtable[val]]

    def checkVal(self, val):
        return val in self.hashtable

    def heapify(self):
        for i in range( (len(self)+1)//2, -1, -1 ):
            self.__bubble_down(i)

    def insert(self, val):
        self.arr.append(val)
        self.hashtable[ self.__getValue(len(self)-1) ] = len(self)-1
        self.__bubble_up(len(self)-1)

    def extractMin(self):
        self.arr[0], self.arr[-1] = self.arr[-1], self.arr[0]
        self.__swap_hash_indices(0, len(self)-1)
        self.hashtable.pop( self.__getValue(len(self)-1) )
        ans = self.arr.pop()
        self.__bubble_down(0)
        return ans

    def updateKey(self, val):
        key = self.key(val)
        idx = self.hashtable[ self.value(val) ]
        old_key = self[idx]
        self.arr[idx] = val
        
        if key < old_key: self.__bubble_up(idx)
        elif key > old_key: self.__bubble_down(idx)
        else: pass

    def __bubble_up(self, idx):
        parent = (idx-1)//2
        while parent >=0 and self[idx] < self[parent]:
            self.__swap_hash_indices(idx, parent)
            self.arr[idx], self.arr[parent] = self.arr[parent], self.arr[idx]
            idx, parent = parent, (parent-1)//2
        
    def __bubble_down(self, idx):
        high = len(self)
        while True:
            l, r = (2*idx)+1, (2*idx)+2
            if r<high:
                if self[l] > self[r]:
                    if self[r] >= self[idx]: break
                    self.__swap_hash_indices( idx, r )
                    self.arr[idx], self.arr[r] = self.arr[r], self.arr[idx]
                    idx = r
                else:
                    if self[l] >= self[idx]: break
                    self.__swap_hash_indices( idx, l )
                    self.arr[idx], self.arr[l] = self.arr[l], self.arr[idx]
                    idx = l
            elif l<high:
                if self[l] >= self[idx]: break
                self.__swap_hash_indices( idx, l )
                self.arr[idx], self.arr[l] = self.arr[l], self.arr[idx]
                idx = l
            else:
                break

2. Union-Find (Disjoint Set)

  • Description : Maintaines a partition of set of objects. Supports two operation find and union.

      1. Find(x) : returns the name of grp to which object belong. O(1)
      2. Union(Ci,Cj): fuse groups Ci and Cj in one group. O(logn)
    
  • Application : Clustering, Kruskals algorithm

  • Algorithm : Maintain a link for each vertex in connected component. Designate any one vertex in component as leader vertex. Other vertex in that component point to its leader vertex. When doing find just return the leader. When doing union, change the leader of object in smaller component to leader of larger component.

  • Union by rank and Path Compression:

      1. All ranks are initialized as 0
      2. During union the leader with smaller rank will point to the leader of larger rank. 
      3. If rank of both leaders are equal then point any one to other one, and increase rank of leader being pointedby 1.
    
    1. Path Compression - During find change leader of all intermediate vertex as the root. No need to change ranks, rank lemma still hold true. - HopCraft and Ullma
    2. Now Union and Find running time is log*n i.e. number of times you need to apply log2 so result is <=1
class UnionFind():
    def __init__(self):
        self.rank  = {}
        self.leader = {}
        
    def __repr__(self):
        return f""" Ranks : {self.rank}, leaders : {self.leader} """
    
    def find(self,x):
        leader = self.leader.get(x, x)
        if leader != x:
            leader = self.find(leader)
            self.leader[x] = leader
        return leader
    
    def getRank(self,x):
        return self.rank.get( x, 0)
    
    def union(self,x,y):
        leader1 = self.find(x)
        leader2 = self.find(y)
        if leader1 == leader2 : return
        if self.getRank(leader1) > self.getRank(leader2):
            self.leader[leader2] = leader1
        elif self.getRank(leader1) < self.getRank(leader2):
            self.leader[leader1] = leader2
        else:
            self.leader[leader1] = leader2
            self.rank[leader2] = 1 + self.getRank(leader2)

3. Trie Data Structure

  • In computer science, a trie, also called digital tree or prefix tree, is a type of search tree, a tree data structure used for locating specific keys from within a set.
from collections import defaultdict
class TrieNode:
    def __init__(self):
        self.children = defaultdict(TrieNode)
        self.end = True
    
    def __repr__(self): return f"{dict(self.children)}"

class Trie:
    def __init__(self, words):
        self.root = TrieNode()
        if len(words) != 0: self.root.end = False
        for word in words: self.add(word)
            
    def add(self, word):
        node = self.root
        for i in range(len(word)):
            if word[i] in node.children:
                node = node.children[ word[i] ]
            else:
                node = node.children[ word[i] ]
                node.end = False
        node.end = True
        
    def search(self, word):
        node = self.root
        for i in range(len(word)):
            if word[i] in node.children: 
                node = node.children[ word[i] ]
            else: return False
        return node.end

4. Self-Balancing Trees (AVL Trees)

  • Balancing factor of node in AVL tree is (Height of left subtree - Height of Right subtree). Balancing property is maintained by using this balancing factor. For balanced tree, balancing factor of node can be +1, 0, or -1.
  • Left Rotate : The arrangement of the nodes on right is transformed into the arrangement on the left node
     p               p                  		  p   
     |               |                       p		  |
     x               x      y      	x    |		  y
    / \        ->   / \      \   ->    / \   y    ->   	 / \
   a   y           a   b      c       a   b   \		x   c
      / \                                      c       / \
     b   c                                	      a   b

  • Right Rotate : The arrangement of the nodes on the left is transformed into the arrangement on the right node.
    p               p                                      p
    |               |                   p                  |
    y               y        x          |                  x
   / \       ->    / \      /     ->    x    y    ->      / \
  x   c           b   c    a           /    / \          a   y
 / \                                  a    b   c            / \
a   b                                                      b   c  
  • Lef-Right and Right-Left Rotate
			Left-Right Rotate
			
	p                        p                       p
	|                        |                       |
	z (2)                    z (2)                   y (0)
       / \                      / \                    /   \
 (-1) x   d        ->          y   d     ->           x     z   
     / \          Left        / \       Right        / \   / \
    a   y        Rotation    x   c      Rotate      a   b c   d
       / \       on x-y     / \         on y-z  
      b   c                a   b
      			
			
			Right-Left Rotate

        p                       p                          p
	|                       |                          | 
	z (-2)                  z (-2)                     y (0) 
       / \                     / \                       /   \
      f   x        ->         f   y         ->          z     x
         / \      Right          / \        Left       / \   / \
	y   c    Rotation       a   x      Rotate     f   a  b  c
       / \	  on x-y           / \     on z-y
      a	  b                       b   c	

4. Self-Balancing Trees (Red-black Trees)

  • Less balanced compared to AVL trees, but involves less rotations during insertions and deletion.
  • Generally used when insertion and deletion is less frequent but search is frequent.
  • A chain of three nodes is not possible in Red-Black tree.

Interesting points about Red-black trees

  • Black height of red-black tree is number of black nodes on a path from root node to leaf node. Leaf nodes are also counted as black nodes. So, a red-black tree of height h has black height >= h/2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment