import java.util.Random;

/**
 * Sequences of integers that support sorting.
 *
 * @author Samuel A. Rebelsky
 * @version 1.1 of September 1998
 */
public class SortableIntSeq {

  // +--------+--------------------------------------------------
  // | Fields |
  // +--------+
 
  /**
   * The elements in the sequence (plus some spare spaces
   * so that we can easily add more elements).
   */
  protected int[] elements;

  
  // +---------------+-------------------------------------------
  // | Basic Methods |
  // +---------------+

  /**
   * Get the value of the ith element of the sequence.
   * Returns the value of the ith element.  If a negative
   * index is given, return 0.
   */
  public int get(int i) {
    // Check for negative indices
    if (i < 0) {
      return 0;
    } // negative index
    // Check for indices outside the range of the array
    else if ((this.elements == null) || (i >= this.elements.length)) {
      return 0;
    } // really large index
    // Everything else
    else {
      return this.elements[i];
    } // everything else
  } // get(int)

  /**
   * Get the ``length'' of the sequence.  In general, this is one
   * higher than the index of the highest element accessed.
   */
  public int length() {
    if (this.elements == null) {
      return 0;
    }
    else {
      return this.elements.length;
    } 
  } // length()
  
  /**
   * Convert the sequence to a string.
   */
  public String toString() {
    // Handle empty sequences
    if (this.elements.length == 0) {
      return "[]";
    }
    // Handle nonempty sequences
    else {
      int i;      // A counter
      String str; // The string we generate
      str = "[" + this.elements[0];
      for (i = 1; i < this.elements.length; ++i) {
        str = str + "," + this.elements[i];
      } // for
      str = str + "]";;
      return str;
    } // nonempty sequence
  } // toString()
  
  /**
   * Set the value of the ith element of the sequence.  
   * Returns the old ith element.   If a negative index
   * is given, does not affect the sequence and returns 0.
   */
  public int set(int i, int val) {
    // Check for negative indices
    if (i < 0) {
      return 0;
    }
    // Make sure the array is big enough
    verifyCapacity(i+1);
    // Get the old value
    int old = elements[i];
    // Fill in the new value
    elements[i] = val;
    // Return the old value
    return old;
  } // set(int,int)
    
  /**
   * Swap the values at positions i and j.
   */
  public void swap(int i, int j) {
    int tmp;  // A temporary value used in swapping
    // Make sure there are sufficiently many elements
    verifyCapacity(j+1);
    // Swap by copying
    tmp = this.elements[i];
    this.elements[i] = this.elements[j];
    this.elements[j] = tmp;
  } // swap(int,int)


  // +---------------------+-------------------------------------
  // | Fill in in elements |
  // +---------------------+

  /**
   * Fill the subsequence [lb .. ub] with the sequence of elements
   * starting with one value and incrementing by inc at every step.  For
   * example, fill(2,5,3,4) puts 3 in position 2, 7 in position 3,
   * 11 in position 4, and 15 in position 5.
   */
  public void fill(int lb, int ub, int start, int inc) {
    int current; // The current value being inserted
    int i;       // A counter
    // Make sure the array is big enough
    verifyCapacity(ub+1);
    // Set the initial value
    current = start;
    // Fill in the values
    for (i = lb; i <= ub; ++i) {
      this.elements[i] = current;
      current += inc;
    } // for
  } // fill(int,int,int,int)
  
  /**
   * Fill the first n elements of a sequence with n random elements.
   */
  public void randomFill(int n) {
    randomFill(0, n-1, 0);
  } // randomFill(int)
  
  /**
   * Fill the subsequence [lb ..ub] with the kth sequence
   * of ``random'' numbers between -1000 and 1000.  If k is
   * 0, uses an unspecified sequence of ``random'' elements.
   */
  public void randomFill(int lb, int ub, int k) {
    randomFill(lb, ub, k, 1000);
  } // randomFill(int, int, int)

  /**
   * Fill the subsequence [lb .. ub] with the kth sequence
   * of ``random'' elements between -cap and cap.  If k is 0, 
   * an unspecified sequence of ``random'' elements is used.
   */
  public void randomFill(int lb, int ub, int k, int cap) {
    int i;      // Counter variable
    Random gen; // A random sequence generator
    // Set up the random sequence generator
    if (k == 0) {
      gen = new Random();
    }
    else {
      gen = new Random(k);
    }
    // Make sure the array has sufficient capacity
    verifyCapacity(ub+1);
    // Fill it with random elements (not too large)
    for (i = lb; i <= ub; ++i) {
      this.elements[i] = gen.nextInt() % cap;
    } // for
  } // randomFill(int,int,int,int)
    

  // +------------------+----------------------------------------
  // | Smallest Methods |
  // +------------------+

  /** 
   * Compute the index of the smallest element in the subsequence
   * given by lower bound lb and upper bound ub.  The subsequence
   * [lb .. ub] must be nonempty.
   */
  public int indexOfSmallest(int lb, int ub) {
    // Make sure there are elements in the sequence
    if (this.elements == null) {
      return Integer.MIN_VALUE;
    }
    // Make sure the upper bound and lower bound are reasonable.
    if (lb < 0) {
       lb = 0;
    }
    if (ub >= this.elements.length) {
       ub = this.elements.length - 1;
    }
    // Our guess as to the index of the smallest element
    int guess = lb;
    // A counter variable
    int i;
    // Look through all subsequent elements
    for (i = lb + 1; i <= ub; ++i) {
      // If the element is smaller than our guess, then
      //   update the guess
      if (this.elements[i] < this.elements[guess]) {
        guess = i;
      } // if
    } // for
    // That's it, we're done
    return guess;
  } // indexOfSmallest(int)

  /** 
   * Compute the smallest element in the sequence.  The sequence
   * must be nonempty.
   */
  public int smallest() {
    // Our guess as to the smallest element
    int guess = this.elements[0];
    // A counter variable
    int i;
    // Look through all subsequent elements
    for (i = 1; i < this.elements.length; ++i) {
      // If the element is smaller than our guess, then
      //   update the guess
      if (this.elements[i] < guess) {
        guess = this.elements[i];
      } // if
    } // for
    // That's it, we're done
    return guess;
  } // smallest()

  /**
   * Put the two smallest elements of the sequence at the beginning
   * of the sequence.  The sequence must have at least two elements.
   */
  public void twoSmallest() {
    // Swap the initial element with the smallest
    swap(0, indexOfSmallest(0,this.length()-1));
    // Swap the next element with the smallest remaining
    swap(1, indexOfSmallest(1,this.length()-1));
  } // twoSmallest()
  
  /**
   * Put the five smallest elements of the array at the beginning of
   * the array (naive method).  The sequence should have at least
   * five elements.
   */
  public void fiveSmallest() {
    // For each index i from 0 to 4, swap the smallest element in
    // [i .. last] with the ith element.
    int i;
    for (i = 0; i < 5; ++i) {
      swap(i, indexOfSmallest(i, this.length()-1));
    } // for
  } // fiveSmallest()
  
  /**
   * Put the five smallest elements of the array at the beginning of
   * the array (better method).  The sequence should have at least
   * five elements.
   */
  public void newFiveSmallest() {
    int i; // Everybody's favorite counter variable
    // Put the first five elements in order using the naive method
    for (i = 0; i < 4; ++i) {
      swap(i, indexOfSmallest(i, 4));
    } // for
    // For each remaining element, if it belongs in the first five
    // (or, more precisely, our estimate of the first five), then
    // put it in the correct place in that sorted sublist.
    for (i = 5; i < this.length(); ++i) {
      // Does the remaining element belong in our guess about the
      // first five elements?
      if (this.elements[i] < this.elements[4]) {
        // If so, put it at the end of that group
        swap(4, i);
        // And then move it to the right place in that group
        insertLast(0,4);
      } // if the ith element belongs in our current guess
    } // for
  } // newFiveSmallest()
  
  
  // +-----------------+-----------------------------------------
  // | Sorting Methods |
  // +-----------------+
  
  /**
   * Sort the current sequence using the legendary insertion
   * sort algorithm.
   */
  public void insertionSort() {
    int i;
    // For each prefix subsequence
    for (i = 1; i < this.length(); ++i) {
      // Sort that prefix subsequence
      insertLast(0, i);
    } // for
  } // insertionSort()
  
  /**
   * Sort the current sequence using the legendary Quicksort
   * algorithm.
   */
  public void quickSort() {
    quickSort(0, this.length()-1);
  } // quickSort()
  
  /**
   * Sort the subsequence given by [lb .. ub] using the legendary
   * Quicksort algorithm.
   */
  public void quickSort(int lb, int ub) {
    // Base case: the subsequence has 0 or 1 elements
    if (ub <= lb) {
      return;
    }
    // Recursive case: at least two elements
    else {
      // Break the subsequence up into smaller elements and
      // larger elements
      int splitter = split(lb,ub);
      // Sort the smaller subsequence
      quickSort(lb, splitter-1);
      // Sort the larger subsequence
      quickSort(splitter+1,ub);
    } // recursive case
  } // quickSort(int,int)
  
  /**
   * Split the subsequence given by [lb .. ub] into smaller and
   * larger elements.  Return the index of the pivot between those
   * elements.
   */
  public int split(int lb, int ub) {
    //  Use the first element of the subsequence as the pivot value.
    int pivotval = this.elements[lb];
    int l=lb; // Elements [lb..l] are all <= pivotval
    int r=ub; // Elements [r..ub] are all > pivotval
    // Keep going until we run out of elements to put in the correct place
    while (l < r) {
      // At this point, we know that 
      //   (1) l < r
      //   (2) Elements [lb..l] are all <= pivotval
      //   (3) Elements [r..ub] are all > pivotval

      // Skip over any large elements in the right half
      while ((this.elements[r] > pivotval) && (r > l)) {
        --r;
      }

      // At this point, we know that
      //    (1) l <= r (we stop moving r left when we hit l or run out
      //                of large elements)
      //    (2) elements [lb..l] are all <= pivotval (we haven't moved l)
      //    (3) elements [r+1..ub] are all > pivotval (by the for loop)
      //    (4) element r is <= pivotval (we either stopped moving when
      //        we hit such an element or r = l and l indexes such an
      //        element)

      // Skip over any small elements in the left half
      while ((this.elements[l] <= pivotval) && (l < r)) {
        ++l;
      }

      // At this point, we know that
      //    (1) l <= r (we stop moving r left when we hit l or run out
      //                of large elements)
      //    (2) elements [lb..l-1] are all <= pivotval (by the for loop)
      //    (3) elements [r+1..ub] are all > pivotval (we haven't moved r)
      //    (4) element r is <= pivotval (we either stopped moving when
      //        we hit such an element or r = l (and l indexes such an
      //        element)
      //    (5) if l < r then element l is > pivotval (by the for loop)
      //    (6) if l = r then element l is <= pivotval
    
      // Do we have a large element in the left and a small element
      // on the right?
      if (this.elements[l] > this.elements[r]) {
        swap(l,r);
      } 
    } // while

    // At this point, we know that
    //    (1) elements [lb..l] are all <= pivotval 
    //    (2) elements [l+1..ub] are all > pivotval

    // Put the pivot in the middle.  Note that at this point, element l is
    // <= pivotval, so this is a safe swap
    swap(lb, l);

    // And we're done
    return l;
  } // split
  
  /**
   * Sort the current sequence using the legendary selection
   * sort algorithm.
   */
  public void selectionSort() {
    // Not yet implemented
  } // selectionSort()
  

  // +----------------+------------------------------------------
  // | Helper Methods |
  // +----------------+

  /**
   * Insert the last element in the subrange [lb .. ub] into the
   * appropriate place in [lb .. ub], assuming that the subrange
   * [lb .. ub-1] is sorted in increasing order.
   */
  protected void insertLast(int lb, int ub) {
    int i; // A counter variable
    // Start at the upper end of the range
    i = ub;
    // As long as the ith element is out of order,
    while ((i > 0) && (this.elements[i] < this.elements[i-1])) {
      // Swap the two out-of-order elements
      swap(i,i-1);
      // Move back one element
      i = i - 1;
    } // while
  } // insertLast(int,int)
  
  /**
   * Increase the capacity of the elements array.
   */
  protected void verifyCapacity(int newcap) {
    // Special case: empty array
    if (this.elements == null) {
      // Create an appropriate sized array
      this.elements = new int[newcap];
      // Fill it with sufficiently many 0 elements
      int i;
      for (i = 0; i < this.elements.length; ++i) {
        this.elements[i] = 0;
      } // for
    } // if the array is empty
    // Normal case: too few elements
    else if (this.elements.length < newcap) {
      // Build a new array of the appropriate size
      int[] new_elements = new int[newcap];
      // Set up a counter
      int j;
      // Copy the existing elements
      for (j = 0; j < this.elements.length; ++j) {
        new_elements[j] = this.elements[j];
      } // for
      // Use 0 for the remaining elements
      for (j = this.elements.length; j < new_elements.length; ++j) {
        new_elements[j] = 0;
      } // for
      // And update elements
      this.elements = new_elements;
    } // if we need to increase the capacity
  } // verifyCapacity(int)

} // class SortableIntSeq

