// Quick and dirty implementation of HashSet using Separate Chaining

public class ChainingHashSet<E> {
    private HashNode[] table;
    private int size;
    private static final double LOAD_FACTOR_THRESHOlD = 0.70;

    public ChainingHashSet(int capacity) {
        // initialize array with load factor of 0.70
        table = new HashNode[(int) (capacity / LOAD_FACTOR_THRESHOlD)];
    }

    public void add(E key) {
        if (contains(key)) return;

        int i = indexOf(key);
        table[i] = new HashNode(key, table[i]);
        size++;
        if (size / table.length > LOAD_FACTOR_THRESHOlD) {
            rehash();
        }

    }

    //rehash
    private void rehash() {
        HashNode[] old_table = table;
        table = new HashNode[table.length * 2]; //double the size (better version is to use a prime number)
        size = 0;
        for (HashNode node : old_table) {
            add((E) node.data);
        }
    }

    public boolean contains(E key) {
        int i = indexOf(key);
        HashNode current = table[i];
        while (current != null) {
            if (current.data.equals(key)) {
                return true;
            }
            current = current.next;
        }
        return false;
    }

    public void remove(E key) {
        if (contains(key)) {
            int i = indexOf(key);
            if (table[i].data.equals(key)) { //removing head
                table[i] = table[i].next;
            } else { //removing non-head
                HashNode current = table[i];
                while (!current.next.data.equals(key)) {
                    current = current.next;
                }
                current.next = current.next.next;
            }
            size--;
        }
    }

    public int size() {
        return size;
    }

    private int indexOf(E key) {
        return Math.abs(key.hashCode() % table.length);
    }

    //linked list node
    private static class HashNode {
        public Object data;
        public HashNode next;

        public HashNode(Object data, HashNode next) {
            this.data = data;
            this.next = next;
        }
    }
}
