Untitled

Anonymous
plain_text
05/01/2026 9:35 PM
10.7 KB
14
Indexable
/**
 * Implement a hash table for SongDB to use. This is the main indexing for
 * SongDB by storing the string records indirectly through handle.java provided
 * by mem man
 *
 * Data: Strings. Hash function: sfold. Collision resolution: quadratic probing.
 *
 * Adapted here for Project 2 so that strings map to graph nodes.
 *
 * @author Leguejou Awunganyi
 * @author Ishita Punna
 * @version 2026-02-08
 */
public class Hash {

    /**
     * Result returned by an insert attempt.
     */
    public static class InsertResult {

        /**
         * True when the table doubled before insertion.
         */
        public boolean resized;

        /**
         * True when the key was already present.
         */
        public boolean duplicate;

        /**
         * True when the key was inserted.
         */
        public boolean inserted;
    }

    /**
     * Helper class representing one slot in the hash table.
     */
    private static class Entry {

        /**
         * Stored key string.
         */
        private String key;

        /**
         * Graph node associated with the key.
         */
        private GraphNode node;

        /**
         * True if this entry has been logically deleted.
         */
        private boolean tombstone;

        /**
         * Create a new Entry with the given key and graph node.
         *
         * @param itemKey  stored key string
         * @param itemNode node associated with the key
         */
        Entry(String itemKey, GraphNode itemNode) {
            key = itemKey;
            node = itemNode;
            tombstone = false;
        }
    }

    /**
     * String for the table name.
     */
    private String name;

    /**
     * Array of table entries.
     */
    private Entry[] table;

    /**
     * Current capacity of the table.
     */
    private int capacity;

    /**
     * Number of active (non-tombstone) records.
     */
    private int count;

    /**
     * Create a new Hash object.
     *
     * @param n     String for the table name
     * @param isize The initial size (and increment size) for the table
     */
    public Hash(String n, int isize) {
        name = n;
        table = new Entry[isize];
        capacity = isize;
        count = 0;
    }

    /**
     * Get the number of active elements in the table.
     *
     * @return current number of live records
     */
    public int size() {
        return count;
    }

    /**
     * Get the current table capacity.
     *
     * @return current capacity
     */
    public int capacity() {
        return capacity;
    }

    /**
     * Get the graph node associated with a string.
     *
     * @param key string to find
     * @return matching graph node, or null if not found
     */
    public GraphNode find(String key) {
        int slot = findSlot(key);
        if (slot < 0) {
            return null;
        }
        return table[slot].node;
    }

    /**
     * Insert a string and its graph node into the table.
     *
     * @param key  string to insert
     * @param node graph node associated with the string
     * @return insertion result information
     */
    public InsertResult insert(String key, GraphNode node) {
        InsertResult result = new InsertResult();
        if (count >= capacity / 2) {
            resize();
            result.resized = true;
        }

        int home = h(key, capacity);
        int firstTomb = -1;

        for (int i = 0; i < capacity; i++) {
            int pos = probe(home, i);
            Entry entry = table[pos];

            if (entry == null) {
                int insertPos = pos;
                if (firstTomb >= 0) {
                    insertPos = firstTomb;
                }

                table[insertPos] = new Entry(key, node);
                count++;
                result.inserted = true;
                return result;
            }

            if (entry.tombstone) {
                if (firstTomb < 0) {
                    firstTomb = pos;
                }
            } 
            
            else if (entry.key.equals(key)) {
                result.duplicate = true;
                return result;
            }
        }

        if (firstTomb >= 0) {
            table[firstTomb] = new Entry(key, node);
            count++;
            result.inserted = true;
        }
        return result;
    }

    /**
     * Remove a string from the table.
     *
     * @param key string to remove
     * @return removed graph node, or null if not found
     */
    public GraphNode remove(String key) {
        int slot = findSlot(key);
        if (slot < 0) {
            return null;
        }
        table[slot].tombstone = true;
        count--;
        return table[slot].node;
    }

    /**
     * Print the hash table contents.
     *
     * @return formatted table string
     */
    public String print() {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < capacity; i++) {
            Entry entry = table[i];
            if (entry != null) {
                builder.append(i).append(": ");
                if (entry.tombstone) {
                    builder.append("TOMBSTONE");
                } 
                
                else {
                    builder.append("|").append(entry.key).append("|");
                }
                builder.append("\r\n");
            }
        }
        String totalMsg = "total " + name.toLowerCase() +
            "s: " + count + "\r\n";
        builder.append(totalMsg);
        return builder.toString();
    }

    /**
     * Find the slot containing the given string.
     *
     * @param key string to find
     * @return slot index, or -1 if not found
     */
    private int findSlot(String key) {
        int home = h(key, capacity);
        for (int i = 0; i < capacity; i++) {
            int pos = probe(home, i);
            Entry entry = table[pos];
            if (entry == null) {
                return -1;
            }
            if (!entry.tombstone && entry.key.equals(key)) {
                return pos;
            }
        }
        return -1;
    }

    /**
     * Quadratic probing function.
     *
     * @param home home slot for the key
     * @param i    probe number
     * @return slot index for this probe/formula
     */
    private int probe(int home, int i) {
        return (home + i * i) % capacity;
    }

    /**
     * Resize the hash table to twice its current capacity and reinsert
     * all active entries.
     */
    private void resize() {
        Entry[] oldTable = table;
        table = new Entry[capacity * 2];
        capacity *= 2;
        count = 0;

        for (Entry entry : oldTable) {
            if (entry != null && !entry.tombstone) {
                placeEntry(entry.key, entry.node);
            }
        }
    }

    /**
     * Place an entry into the current table without resizing.
     *
     * @param key  string to place
     * @param node graph node to store
     */
    private void placeEntry(String key, GraphNode node) {
        int home = h(key, capacity);
        for (int i = 0; i < capacity; i++) {
            int pos = probe(home, i);
            if (table[pos] == null) {
                table[pos] = new Entry(key, node);
                count++;
                return;
            }
        }
    }

    /**
     * Compute the hash function. Uses the "sfold" method from the 
     * OpenDSA module on hash functions.
     *
     * @param s The string that we are hashing
     * @param m The size of the hash table
     * @return The home slot for that string
     */
    public int h(String s, int m) {
        long sum = 0;
        long mult = 1;
        for (int i = 0; i < s.length(); i++) {
            mult = (i % 4 == 0) ? 1 : mult * 256;
            sum += s.charAt(i) * mult;
        }
        return (int) (Math.abs(sum) % m);
    }
}
Editor is loading...
Leave a Comment