# algorithms.py
# Walker M. White (wmw2)
# November 1, 2015
""" Module with algorithms from the sequence algorithm design slides."""
import random


def dnf(b, h, k):
    """Returns: Partition points for the Dutch National Flag
    
    The Dutch National Flag algorithm arranges the elements of b[h..k]
    so that negatives are first, then 0s, then positives. It returns a
    tuple (i,j)  representing the two partition points.
    
    Parameter b: The list to rearrange
    Precondition: b is a mutable sequence (e.g. a list).
    
    Parameter h: The starting point to sort
    Precondition: h is an int and a valid position in b
    
    Parameter k: The ending poing to sort
    Precondition: k is an int and a valid position in b
    """
    assert type(b) == list, `b`+' is not a list'
    assert 0 <= h and h < len(b), `h`+' is not a valid position in the list'
    assert 0 <= k and k < len(b), `k`+' is not a valid position in the list'

    # 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:
            _swap(b,i-1,t)
            t = t+1
        elif b[i-1] == 0:
            i = i-1 
        else: 
            _swap(b,i-1,j)
            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)


# NOTE: This uses a DIFFERENT invariant than the lab
def partition(b, h, k):
    """Returns: The new position of pivot in partitioned list b[h..k].
    
    The pivot is the initial value x = b[h].  This function rearranges the
    list so that elements <= x are before the pivot and elements >= x are
    after the pivot.

    Parameter b: The list to rearrange
    Precondition: b is a mutable sequence (e.g. a list).
    
    Parameter h: The starting point to sort
    Precondition: h is an int and a valid position in b
    
    Parameter k: The ending poing to sort
    Precondition: k is an int and a valid position in b
    """
    assert type(b) == list, `b`+' is not a list'
    assert 0 <= h and h < len(b), `h`+' is not a valid position in the list'
    assert 0 <= k and k < len(b), `k`+' is not a valid position in the list'
    
    # position i is end of first paritition range
    i = h
    # position j is BEFORE beginning of second partition range
    j = k
    
    # Find the first element in the list.
    x = b[h]
        
    # invariant: b[h..i-1] < x, b[i] = x, b[i+1..j] unknown, and  b[j+1..k] >= x
    while i < j:
        if b[i+1] >= x:
            # Move this to the end of the block.
            _swap(b,i+1,j)
            j = j - 1
        else:   # b[i+1] < x
            _swap(b,i,i+1)
            i = i + 1
        
    # post: b[h..i-1] < x, b[i] is x, and b[i+1..k] >= x
    return i


def fom(b, h, k):
    """Returns: Partition points for the Flag of Mauritius
    
    The Mauritius algorithm arranges the elements of b[h..k] into four groups:
    negative odds, negative evens, nonnegative odds, and nonnegative evens,
    in that order. It returns a tuple (r,t, s) representing the three partition 
    points of the four groups.
    
    Parameter b: The list to rearrange
    Precondition: b is a mutable sequence (e.g. a list).
    
    Parameter h: The starting point to sort
    Precondition: h is an int and a valid position in b
    
    Parameter k: The ending poing to sort
    Precondition: k is an int and a valid position in b
    """
    assert type(b) == list, `b`+' is not a list'
    assert 0 <= h and h < len(b), `h`+' is not a valid position in the list'
    assert 0 <= k and k < len(b), `k`+' is not a valid position in the list'

    # Loop variables to satisfy the invariant
    r = h
    s = h
    i = h
    t = k+1
    
    # inv: b[h..r-1] < 0 and odd, b[r..s-1] < 0 and even, b[s..i-1] >= 0 and odd
    #      b[i..t-1] unknown, and b[t..k] >= 0 and even.
    while i < t:
        if (b[i] < 0 and b[i] % 2 == 1):
            _swap(b,i,r)
            if r != s:  # Need a second swap in this case.
                _swap(b,i,s)
            r = r+1
            s = s+1
            i = i+1
        elif (b[i] < 0 and b[i] % 2 == 0):
            _swap(b,i,s)
            s = s+1
            i = i+1 
        elif (b[i] >= 0 and b[i] % 2 == 1):
            i = i+1
        else: # >= 0 and even
            _swap(b,i,t-1)
            t = t-1
    
    # post: b[h..r-1] < 0 and odd, b[r..s-1] < 0 and even,
    #       b[s..t-1] >= 0 and odd, and b[t..k] >= 0 and even.
    # Return dividers as a tuple
    return (r, s, t)


# HELPER FUNCTION
def _swap(b, h, k):
    """Procedure swaps b[h] and b[k]
    
    Parameter b: The list to rearrange
    Precondition: b is a mutable sequence (e.g. a list).
    
    Parameter h: The first position to swap
    Precondition: h is an int and a valid position in b
    
    Parameter k: The second position to swap
    Precondition: k is an int and a valid position in b
    """
    # We typically do not enforce preconditions on hidden helpers
    temp = b[h]
    b[h] = b[k]
    b[k] = temp


# Linear search    
def linear_search(b,c):
    """Returns: index of first occurrence of c in b; -1 if not found.
    
    Parameter b: The sequence to search
    Precondition: b is a sequence
    
    Parameter c: The value to search for
    Precondition: NONE (c can be any value)"""
    # Quick way to check if a sequence
    assert len(b) >= 0, `b`+' is a not a sequence (list, string, or tuple)'

    # Store in i the index of the first c in b[0..]
    i = 0
    
    # invariant: c is not in b[0..i-1]
    while i < len(b) and b[i] != c:
        i = i + 1;
        
    # post: b[i] == c OR (i == len(b) and c is not in b[0..i-1])
    return i if i < len(b) else -1


# Binary search
def binary_search(b,c):
    """Returns: index of first occurrence of c in b; -1 if not found.
    
    Parameter b: The sequence to search
    Precondition: b is a SORTED sequence
    
    Parameter c: The value to search for
    Precondition: NONE (c can be any value)"""
    # Quick way to check if a sequence; CANNOT easily check sorted
    assert len(b) >= 0, `b`+' is a not a sequence (list, string, or tuple)'

    # Store in i the value BEFORE beginning of range to search
    i = 0
    # Store in j the end of the range to search (element after)
    j = len(b)
    # The middle position of the range
    mid = (i+j)/2
    
    # invariant; b[0..i-1] < c, b[i..j-1] unknown, b[j..] >= c 
    while j > i:
        if b[mid] < c:
            i = mid+1
        else:     # b[mid] >= c
            j = mid
            
        # Compute a new middle.
        mid = (i+j)/2
    
    # post: i == j and b[0..i-1] < c and b[j..] >= c
    return i if (i < len(b) and b[i] == c) else -1


def isort(b):
    """Insertion Sort: Sorts the array b in n^2 time
    
    Parameter b: The sequence to sort
    Precondition: b is a mutable sequence (e.g. a list)."""
    assert type(b) == list, `b`+' is not a list'
    
    # Start from beginning of list
    i = 0
    
    # inv: b[0..i-1] sorted    
    while i  < len(b):
        _push_down(b,i)
        i = i + 1
    
    # post: b[0..len(b)-1] sorted


# HELPER FUNCTION
def _push_down(b, k):
    """Moves the value at position k into its sorted position in b[0.k-1].
    
    Parameter b: The list to rearrange
    Precondition: b is a list, with b[0..k-1] sorted
    
    Parameter k: The position to push down into b[0..k-1]
    Precondition: k is an int and a valid position in b"""
    # We typically do not enforce preconditions on hidden helpers

    # Start from position k
    j = k

    # inv: b[j..k] is sorted
    while j > 0:
        if b[j-1] > b[j]:
            _swap(b,j-1,j)
        j = j - 1
    # post: b[0..k] is sorted     


def ssort(b):
    """Selection Sort: Sorts the array b in n^2 time
    
    Parameter b: The sequence to sort
    Precondition: b is a mutable sequence (e.g. a list)."""
    assert type(b) == list, `b`+' is not a list'
    
    # Start from beginning of list
    i = 0

    # inv: b[0..i-1] sorted    
    while i < len(b):
        index = _min_index(b,i);
        _swap(b,i,index)    
        i = i+1
    # post: b[0..len(b)-1] sorted


# HELPER FUNCTION
def _min_index(b, h):
    """Returns: The index of the minimum value in b[h..]
    
    Parameter b: The sequence to search
    Precondition: b is a mutable sequence (e.g. a list)."""
    # We typically do not enforce preconditions on hidden helpers

    # Start from position h
    i = h
    index = h;
    
    # inv: index position of min in b[h..i-1]
    while i < len(b):
        if b[i] < b[index]:
            index = i
        i = i+1
        
    # post: index position of min in b[h..len(b)-1]
    return index


def qsort(b):
    """Quick Sort: Sorts the array b in n log n average time
    
    Parameter b: The sequence to sort
    Precondition: b is a mutable sequence (e.g. a list)."""
    assert type(b) == list, `b`+' is not a list'
    
    # Send everything to the recursive helper
    _qsort_helper(b,0,len(b)-1)


def _qsort_helper(b, h, k):
    """Quick Sort: Sorts the array b[h..k] in n log n average time
    
    Parameter b: The sequence to sort
    Precondition: b is a mutable sequence (e.g. a list).
    
    Parameter h: The starting point to sort
    Precondition: h is an int and a valid position in b
    
    Parameter k: The ending poing to sort
    Precondition: k is an int and a valid position in b
    """
    # We typically do not enforce preconditions on hidden helpers
    if k-h < 1:            # BASE CASE
        return 
    
    # RECURSIVE CASE
    j = partition(b, h, k)
    # b[h..j-1] <= b[j] <= b[j+1..k]
    # Sort b[h..j-1]  and  b[j+1..k]
    _qsort_helper(b, h, j-1)
    _qsort_helper(b, j+1, k)


def roll(p):
    """Returns: a random int in 0..len(p)-1; i returned with prob p[i].
    
    Parameter p: The die value distribution
    Precondition: p a list of positive floats that sum to at least 1."""
    # Do not assert precondition; too complicated
    
    r = random.random()     # r in [0,1)
    # Think of interval [0,1] as divided into segments of size p[i]
    # Store into i the segment number in which r falls.
    i = 0
    sum_of = p[0]
    while r >= sum_of:
        sum_of = sum_of + p[i+1]
        i = i + 1
        
    return i


def scramble(b):
    """Scrambles the list to resort again
    
    Parameter b: The list to scramble
    Precondition: b is a mutable sequence (e.g. a list)."""
    assert type(b) == list, `b`+' is not a list'

    # Start from the beginning
    i = 0

    # inv: b[0..i-1] is scrambled
    
    while i < len(b):
        size = len(b)-i
        pos  = int(random.random()*size)
        _swap(b,i,i+pos)
        i = i+1
    # post: b[0..len(b)] is scrambled