/*
 * Author: Sriram Pemmaraju
 * Date: September 29, 2008
 * This is a simple implementation of hashing with chaining
 */

public class ChainingHashTable {

   private StringLinkList[] hashArray;  // array of singly linked lists of strings
   private int M;			// size of the hash table	


/**
 * Constructor for the ChainingHashTable class
 * 
 * @param n is the user's expectation of how many items they expect to store 
 */
 public ChainingHashTable(int n) {

	M = n/3;	// allows for a load factor of 3
      	hashArray = new StringLinkList[M];  	// create hash table 
	
	// Fill each individual slot in the hash table array with empty
	// linked lists
      	for(int j = 0; j < M; j++)          		// fill array
         	hashArray[j] = new StringLinkList();  	// with empty lists
      }


/**
 * Displays the contents of the hash table
 * 
 */
 public void displayTable() {
	// Scan each slot in the hash table
      	for(int j = 0; j < M; j++) {
         	System.out.print(j + ". "); // display cell number
         	hashArray[j].displayList(); // display list
         }
      }

/**
 * Prints the size-distribution of the linked lists in the different slots of the table
 * 
 */
 public void printDistribution() {

	// First scan each slot in the hash table to compute the total number of elements
	// in the hash table
	int sum = 0;
      	for(int j = 0; j < M; j++) 
		sum += hashArray[j].size();

	// The length of a list can range from 0 to sum, though it is quite unlikely that
	// there will be lists as long as sum. We expect each list to have size 3, so let us
	// report the number of lists that have sizes 0, 1, 2,..., 10, and more than 10.
	// The next loop computes the distribution
	int[] distribution = new int[12];
	for(int j = 0; j < M; j++)
	{
		int size = hashArray[j].size();
		if(size > 10)
			distribution[11]++;
		else
			distribution[size]++;
	}

	// Print the distribution
	for(int j  = 0; j < 12; j++)
		System.out.println(distribution[j]);
	
      }
		


/**
 * Computes the hash value of a given string. 
 * Assumes that the string consists of lower case letters, upper case letters, and 


/**
 * Computes the hash value of a given string. 
 * Assumes that the string consists of lower case letters, upper case letters, and 
 * digits. Views the string as a base-32 number and returns equivalent
 * decimal value modulo 32. Uses bit shifting for multiplying
 * by 32 and Horner's rule for reducing number of multiplications
 * 
 * @param s is the String whose hash value needs to be computed
 * @return an int value in the range 0..M-1 that is the hash value
 *         of the given string s
 */
 public int hash(String s) {
   	int hashValue = 0;
        for(int i = 0; i < s.length(); i++) {
        	// Also note that we are now bit-shifting by 2 to the 6th or 64.
                // Consider digits and map these to values in the range 1 - 10
		if((s.charAt(i) >= '0') && (s.charAt(i) <= '9'))
                	hashValue = ((hashValue << 6) + (s.charAt(i) - '0') + 1) % M;
                // Consider upper case characters and map these to values in the range 11 - 36 
                else if((s.charAt(i) >= 'A') && (s.charAt(i) <= 'Z'))
                	hashValue = ((hashValue << 6) + (s.charAt(i) - 'A') + 11) % M;
                // Consider lower case characters and map these to values in the range 37 - 62 
                else if((s.charAt(i) >= 'a') && (s.charAt(i) <= 'z'))
                	hashValue = ((hashValue << 6) + (s.charAt(i) - 'a') + 37) % M;

         }

         return hashValue;
 }


/**
 * Inserts a given string into the hash table.
 * 
 * @param s is the String which needs to be inserted into the hash table
 */
 public void insert(String s) {
      int hashVal = hash(s);   		// computes the hash value of the given string 
      hashArray[hashVal].insertFirst(s); 	// insert s into the linked list stored at hashVal
      }  // end insert()


/**
 * Deletes the given string into the hash table.
 * 
 * @param s is the String which needs to be deleted from the hash table
 */
  public void delete(String s) {
      int hashVal = hash(s);   	// computes the hash value of the given string
      hashArray[hashVal].delete(s); 	// delete s from the linked list stored at hashVal
  }  // end delete()


/**
 * Finds the given string into the hash table.
 * 
 * @param s is the String which needs to be found in the hash table
 * @return the StringLink object that contains s is returned if
 *         s is in the table; otherwise null is returned
 */
   public StringLink search(String s) {
      int hashVal = hash(s);   		// computes the hash value of the given string
      return hashArray[hashVal].find(s);  	// looks for s in the linked list at hashVal
   }
}  // end class ChainingHashTable
