# algorithms.py
# Walker M. White (wmw2), Lillian Lee (LJL2), Steve Marschner (srm2)
# Apr 3, 2013
""" Module with algorithms from the sequence algorithm design slides."""
import cunittest2


def qsort(b, h, k):
    """Quick Sort: sorts the array b[h..k] in n log n average time

    Precondition: b is a mutable sequence (e.g. a list).
    h and k are valid positions in b, k >= h-1."""
    if k<h:    # Base case: no possible pivot point
        return # This "return" just stops execution

    # RECURSIVE CASE
    i = partition(b, h, k)
    # b[h..i-1] <= b[i] <= b[i+1..k]
    qsort(b, h, i-1)
    qsort(b, i+1, k)


# NOTE: This uses a DIFFERENT invariant than the lab
def partition(b, h, k):
    """Partitions list b[h..k] around a pivot x = initial value of b[h]
    Returns: pivot position i

    Let the pivot value x be the initial value of b[h].  Rearrange b[h..k] so
    that there is an i where b[h..i-1] <= x, b[i]=x; b[i+1..k] >=x.

    Precondition: b is a mutable sequence (e.g. a list).
    k>=h are both valid indices in b."""

    # position i contains the pivot value, we have nothing in b[h..i-1]
    i = h; x = b[i]
    # position j is beginning of second partition range, which is currently empty
    j = k + 1

    # invariant: b[h..i-1] <= x, b[i] = x, b[i+1..j-1] unknown, and  b[j..k] >= x
    while i < j-1: # still have unknowns left
        if b[i+1] >= x:
            # move second partition range left by one
            b[i+1], b[j-1] = b[j-1], b[i+1]
            j = j - 1
        else:   # move first partition range right by one and update i
            b[i], b[i+1] = b[i+1], b[i]
            i = i + 1

    # post: b[h..i-1] <= x, b[i] is x, and b[i+1..k] >= x
    return i


def dnf(b, h, k):
    """Dutch National Flag algorithm to arrange the elements of b[h..k]

    Returns: partition points as a tuple (i,j)

    Precondition: b is a mutable sequence (e.g. a list).
    h and k are valid positions in b, k>=h-1."""

    # Loop variables to satisfy the invariant
    t = h
    j = k
    i= k+1
    # inv: b[h..t-1] < 0, b[t..i-1] unknown, b[i..j] = 0, and b[j+1..k] > 0
    while t < i:
        if b[i-1] < 0:
            b[i-1], b[t] = b[t], b[i-1]
            t = t+1
        elif b[i-1] == 0:
            i = i-1
        else:
            b[i-1], b[j] = b[j], b[i-1]
            i = i-1
            j = j-1

    # post: b[h..i-1] < 0, b[i..j] = 0, and b[j+1..k] > 0
    # Return dividers as a tuple
    return (i, j)


if __name__ == '__main__':
    list1 = [3, 5, 0, 4, -1, 6, 2, 0, 3, 8, -1]
    list2 = [9,8,7,2,0,-1,-3,-7, 0]

    #test partition
    for mylist in [list1[:], list2[:], [7,1], [7,8], [0]]:
        print 'mylist before partition:', mylist
        j = partition(mylist,0,len(mylist)-1)
        print "after partition, pivot index is:", j, ", mylist is", mylist
        for k in range(j):  # verify first partition range
            cunittest2.assert_true(mylist[k] <= mylist[j])
        for k in range(j+1, len(mylist)):  # verify second partition range
            cunittest2.assert_true(mylist[k] >= mylist[j])
    print 'tests of partition passed'

    # test qsort
    # use separate loop, since sort and partition change the lists
    for mylist in [list1[:], list2[:], [7,1], [7,8], [0], []]:
        copylist = mylist[:]
        qsort(mylist, 0, len(mylist)-1)
        copylist.sort()
        cunittest2.assert_equals(copylist, mylist)
        # # inv: for indices i in 1..k-1, mylist[i-1] >= mylist[i]
        # for k in range(1, len(mylist)-1):
        #     cunittest2.assert_true(mylist[k-1] <= mylist[k])
    print 'test of qsort passed'

    #test dnf
    for mylist in [list1[:], list2[:], [7,1], [7,-8], [0], []]:
        print 'mylist before dnf', mylist
        (i,j) = dnf(mylist, 0, len(mylist)-1)
        print 'mylist after dnf',mylist
        for k in range(i):
            cunittest2.assert_true(mylist[k]<0)
        for k in range(i,j+1):
            cunittest2.assert_true(mylist[k]==0)
        for k in range(j+1,len(mylist)):
            cunittest2.assert_true(mylist[k]>0)
    print 'test of dnf passed'

    print 'Everything is OK'