import random
import time
import sys

def partition(L, first, last):
    swap(L, first, random.randint(first, last))
    # We pick the element L[first] as the "pivot" around which we partition the list
    p = first

    # We process the rest of the elements, one-by-one, in left-to-right order
    for current in range(p+1, last+1):
        # If L[current] is smaller than the pivot, it needs to move into the first block,
        # to the left of the pivot. 
        if L[current] < L[p]:
            swap(L, current, p+1)
            swap(L, p, p+1)
            p = p + 1

    return p

def generalQuickSort(L, first, last):
    # Base case: if first == last, then there is only one element in the
    # slice that needs sorting. So there is nothing to do.

    # Recursive case: if there are 2 or more elements in the slice L[first:last+1]
    if first < last:
        # Divide step: partition returns an index p such that 
        # first <= p <= last and everthing in L[first:p] is <= L[p]
        # and everything in L[p+1:last+1] is >= L[p]
        p = partition(L, first, last)

        # Conquer step
        generalQuickSort(L, first, p-1)
        generalQuickSort(L, p+1, last)

        # Combine step: there is nothing left to do!

def quickSort(L):
    generalQuickSort(L, 0, len(L)-1)

def swap(L, i, j):
    temp = L[i]
    L[i] = L[j]
    L[j] = temp


def selectionSort(L):
    n = len(L)

    for i in range(n):
        m = min(L[i:])
        j = i + L[i:].index(m)
        swap(L, i, j)

    return L

# Assumes that L[first:mid+1] is sorted and also
# that L[mid: last+1] is sorted. Returns L with L[first: last+1] sorted

def merge(L, first, mid, last):
    
    i = first # index into the first half
    j = mid + 1 # index into the second half

    tempList = []
    
    # This loops goes on as long as BOTH i and j stay within their
    # respective sorted blocks
    while (i <= mid) and (j <= last):
        if L[i] <= L[j]:
            tempList.append(L[i])
            #print L[i], "from the first block"
            i += 1
        else:
            tempList.append(L[j])
            #print L[j], "from the second block"
            j += 1
            
    # If i goes beyond the first block, there may be some elements
    # in the second block that need to be copied into tempList.
    # Similarly, if j goes beyond the second block, there may be some
    # elements in the first block that need to be copied into tempList
    if i == mid + 1:
        tempList.extend(L[j:last+1])
        #print L[j:last+1], "some elements in second block are left over"
    elif j == last+1:
        tempList.extend(L[i:mid+1])
        #print L[i:mid+1], "some elements from first block are left over"
        
    L[first:last+1] = tempList
    #print tempList
    

# The merge sort function; sorts the sublist L[first:last+1]    
def generalMergeSort(L, first, last):
    # Base case: if first == last then it is already sorted
    
    # Recursive case: L[first:last+1] has size 2 or more
    if first < last:
        # divide step
        mid = (first + last)/2
        
        # conquer step
        generalMergeSort(L, first, mid)
        generalMergeSort(L, mid+1, last)
        
        # combine step
        merge(L, first, mid, last)
        #print L[first:last+1], L
        
        
# Wrapper function
def mergeSort(L):
    generalMergeSort(L, 0, len(L)-1)
        

#Main Program
sys.setrecursionlimit(100000)
# Create random list
#N = 100000
N = 10000
L = []
LCopy = []
for i in range(N):
    #x = random.randint(1, 1000000)
    x = i
    L.append(x)
    LCopy.append(x)
print "Finished constructing the lists..."

startTime = time.time()
mergeSort(L)
endTime = time.time()
print "Time for Merge Sort is: ", endTime - startTime


startTime = time.time()
quickSort(LCopy)
endTime = time.time()
print "Time for Quick Sort is: ", endTime - startTime
