Algorithms and Data Structures

A range of code relating to Algorithms and Data Structures

Hash Table

The code for this algorithm can be found in Question 1/q1.py

This adds keys to a hash table of size 19 using the hash function defined by h(k)=6k+3 mod 19

In the first implementation hash_quadratic collisions are handled by probing

def hash_quadratic(d):
    #initialize table
    table = ["-"]*19
    flag=0
    #consider each integer k in the input
    for k in d:
        flag=0
        #if k is already in the table this is a duplicate so move to next integer in the input
        #note this check for a duplicate is using the functionality of python rather than checking using a linear probe
        if k in table:
            continue
        #apply the hash function
        i = (6*k+3) % 19
        init=i
        #initialize count that checks whether linear probe has considered each bucket and is now full
        count = 0
        #while bucket is already filled
        while table[i] != "-":
            #move to next bucket
            i = (init+count*count) % 19
            #increment count
            count += 1
            #if table is full
            if count == 18:
                flag=1
                #can return table as nothing further can be added
                break
        if flag==1:
            continue

        #table[i] is empty so k can be added here
        table[i] = k
    #now each part of the input has been considered return the table
    return table

In the second implementation, hash_double collisions are handled using double hashing with the secondary hash function h'(k)=11-(k mod 11)

def hash_double(d):
    #initialize table
    table = ["-"]*19
    flag=0
    #consider each integer k in the input
    for k in d:
        flag=0
        #if k is already in the table this is a duplicate so move to next integer in the input
        #note this check for a duplicate is using the functionality of python rather than checking using a linear probe
        if k in table:
            continue
        #apply the hash function
        i = (6*k+3) % 19
        init=i
        #initialize count that checks whether linear probe has considered each bucket and is now full
        count = 1
        #while bucket is already filled
        while table[i] != "-":
            #move to next bucket
            i = (init+count*(11-(k%11))) % 19
            #increment count
            count += 1
            #if table is full
            if count == 20:
                flag=1
                #can return table as nothing further can be added
                break

        if flag==1:
            continue
        #table[i] is empty so k can be added here
        table[i] = k
    #now each part of the input has been considered return the table
    return table

Ephemeral numbers

This code calculates the number of ephemeral numbers between two values.

A positive integer is k-ephemeral if its k-descendent sequence ends in 1, otherwise it is k-eternal.

The k-descendant sequence of a positive integer begins with the integer and then each successive term is the k-child of the previous one. The sequence terminates if it reaches a 1 or a term is repeated.

The k-child of a positive integer is the number obtained by taking the kth power of each digit and adding them.

This is implemented using the following function

def count_ephemeral(n1,n2,k):
    # Create a list of the powers required for the given value of k
    power_list=[x**k for x in range (10)]
    # Count keeps track of the number of ephemeral numbers
    count=0
    # Create two tables, one to store the eternal numbers, and one to store the ephemeral numbers
    eternal=set()
    eph=set()
    for n in range (n1,n2):
        # Current sequence stores all the values in the current sequence
        current_seq=set()
        # This checks if n has been seen before or if it is 1, in any of those cases it will break out of the loop
        while n not in current_seq and n not in eternal and n not in eph and n!=1:
            # Add the new number to the current sequence
            current_seq.add(n)
            # Calculate the k child of n
            k_child=0
            while n:
                digit = n % 10
                k_child+=power_list[digit]
                n //=10
            n=k_child
        # If n is an ephemeral number add it, and the rest of the sequence to the list of ephemeral numbers
        if n==1 or n in eph:
            eph.update(current_seq)
            count+=1
        # Otherwise add the sequence to the list of eternal numbers
        else:
            eternal.update(current_seq)
    return count

Sum and product expression

A sum and product expression is a statement containing positive integers, brackets and the plus and times operators. A sum and product expression i said to be good is none of the brackets are redundant. This function decides whether a sum and product expression is good. It is implemented using stacks and queues, the code for defining how these work is left out for brevity.

def good_expression(expression):
    # For completeness, remove all the spaces from the string just in case there are any
    expression=expression.replace(" ", "")
    # Initialise flags for if the statement is valid, if a close bracket was the last term and if the stack became empty on the last loop
    flag=0
    empty=False
    valid = True
    # Initialise a stack to store the operators
    data=Stack()

    for i in expression:
        count=0
        # The below if statement will trigger if a close bracket occurred on the last loop
        if flag==1 and empty==False:
            after = i
            before = b
            # If there isn't a multiplication on either of the sides of a pair of brackets, the statement is invalid
            if before!="*" and after!="*":
                valid=False
                break
        # If empty is True, then the first bracket of the pair was the first thing in the string, so there would need to be multiplication on the other side
        elif empty:
            after = i
            if after!="*":
                valid=False
        flag=0
        empty=0
        # If I is an operator or open bracket, just push it to the stack
        if i=="*" or i=="+" or i=="(":
            data.push(i)
        # But if i is a close bracket, more needs to be done
        if i==")":
            # Check if the stack is empty, as I will be popping from it, if it is empty, then there is an error
            if data.isEmpty()==True:
                valid=False
                break
            # Temporarily make valid false, for the purpose of checking the symbols in the bracket
            valid=False
            b=data.pop()
            while b!="(" and data.isEmpty()==False:
                # There needs to be an addition somewhere in the bracket, otherwise it is unnecessary
                if b=="+":
                    valid=True
                count+=1
                b = data.pop()
            # If there wasn't an addition, then break out of the loop ad the expression is bad
            if valid==False:
                break
            # If the bracket only had one symbol in it, it is unnecessary, and so the expression is bad
            if count==0:
                valid=False
                break
            # If the stack is empty, set the flag for the next element to check against
            if data.isEmpty()==True:
                empty=True
                flag=1
                continue
            b=data.top()
            flag=1
    # If the flag was set on the last element
    if flag==1:
        # If the symbol before the brackets isn't a multiply, then the statement is false
        if b!="*":
            valid=False

    return valid

Hybrid Merge Sort

For this algorithm, instead of recursively calling Merge Sort until the list to be sorted has length 1, we will implement Selection Sort to sort any list of length 4 or less. This algorithm will output a sorted list from largest to smallest.

First I implemented a function to perform Selection Sort

def SelectionSort(list):
    for i in range (0,len(list)):
        elem=list[i]
        pos=i
        for j in range(i+1,len(list)):
            if list[j]>=elem:
                elem=list[j]
                pos=j
        list[i], list[pos] = list[pos], list[i]
    return list

Then the function which performs Merge Sort and calls the Selection Sort function when appropriate

def HybridSort(nlist):
    # If the length of the list is less than or equal to 4, then sort using selection sort
    if len(nlist)<=4:
        nlist=SelectionSort(nlist)
    # Otherwise sort using merge sort, unless the list is of length 1
    if len(nlist)>1 and len(nlist)>4:
        # Find the middle of the list
        mid = len(nlist)//2
        # Use this to create two lists
        lefthalf = nlist[:mid]
        righthalf = nlist[mid:]

        HybridSort(lefthalf)
        HybridSort(righthalf)

        # Merging
        i=j=k=0
        # While the lists are not exhausted
        while i < len(lefthalf) and j < len(righthalf):
            # If the left half first index is greater than right half, add it to the output list
            if lefthalf[i] >= righthalf[j]:
                nlist[k]=lefthalf[i]
                # Increment the value of i so that the element would not be added multiple times
                i=i+1
            else:
                # Other wise the right half first index is larger, so add that
                nlist[k]=righthalf[j]
                # And again increment the index
                j=j+1
            # Increment k to jump to the next index in nlist
            k=k+1
        # If the loop has broken out to here then one of the lists is empty
        # Loop adding all the remaining elements of the lefthalf list, if it is empty then just move on
        while i < len(lefthalf):
            nlist[k]=lefthalf[i]
            i=i+1
            k=k+1
        # Loop adding all the remaining elements of the righthalf list, again, if it is empty then just move on
        while j < len(righthalf):
            nlist[k]=righthalf[j]
            j=j+1
            k=k+1