329 lines
9.1 KiB
Java
329 lines
9.1 KiB
Java
package net.abhinavsarkar.algorist;
|
|
|
|
import java.util.Iterator;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Optional;
|
|
|
|
public class OpenAddressingHashTable<K, V> implements Map<K, V>
|
|
{
|
|
|
|
private static final Entry REMOVED = new Entry(0,0);
|
|
private Object[] store;
|
|
private final double loadFactor;
|
|
private final ProbingStrategy probingStrategy;
|
|
private Entry<K, V> head;
|
|
private Entry<K, V> tail;
|
|
private int size;
|
|
private int actualSize;
|
|
|
|
public OpenAddressingHashTable(int initialCapacity, double loadFactor, ProbingStrategy probingStrategy) {
|
|
this.store = new Object[nextPowerOf2(initialCapacity)];
|
|
this.loadFactor = loadFactor;
|
|
this.probingStrategy = probingStrategy;
|
|
}
|
|
|
|
public OpenAddressingHashTable(int initialCapacity, double loadFactor)
|
|
{
|
|
this(initialCapacity, loadFactor, new LinearProbing());
|
|
}
|
|
|
|
public OpenAddressingHashTable(int initialCapacity) {
|
|
this(initialCapacity, 0.75);
|
|
}
|
|
|
|
public OpenAddressingHashTable() {
|
|
this(16);
|
|
}
|
|
|
|
private static int nextPowerOf2(int num)
|
|
{
|
|
return num == 1 ? 1 : Integer.highestOneBit(num - 1) * 2;
|
|
}
|
|
|
|
@Override
|
|
public Optional<V> get(K key) {
|
|
int trial = 0;
|
|
int baseIndex = calcIndex(key, store.length);
|
|
int firstRemovedIndex = -1;
|
|
while (trial < store.length)
|
|
{
|
|
int index = probingStrategy.nextIndex(baseIndex, trial, store.length);
|
|
Entry<K,V> entry = (Entry<K,V>) store[index];
|
|
if (entry == null)
|
|
{
|
|
return Optional.empty();
|
|
}
|
|
else if (entry == REMOVED && firstRemovedIndex == -1) {
|
|
firstRemovedIndex = index;
|
|
}
|
|
else if (entry.getKey().equals(key))
|
|
{
|
|
if (firstRemovedIndex != -1) {
|
|
store[firstRemovedIndex] = entry;
|
|
store[index] = REMOVED;
|
|
}
|
|
return Optional.of(entry.getVal());
|
|
}
|
|
trial++;
|
|
}
|
|
return Optional.empty();
|
|
}
|
|
|
|
@Override
|
|
public void put(K key, V val) {
|
|
if (actualSize >= loadFactor * store.length) {
|
|
resize();
|
|
}
|
|
doPut(new Entry<>(key, val), store, false);
|
|
}
|
|
|
|
private void resize() {
|
|
Object[] nStore = new Object[store.length * 2];
|
|
for (LinkedHashTableIterator iterator = new LinkedHashTableIterator(); iterator.hasNext();)
|
|
{
|
|
Entry<K, V> entry = iterator.next();
|
|
doPut(entry, nStore, true);
|
|
}
|
|
store = nStore;
|
|
actualSize = size;
|
|
}
|
|
|
|
private void doPut(Entry<K,V> nEntry, Object[] store, boolean resize) {
|
|
int trial = 0;
|
|
int baseIndex = calcIndex(nEntry.getKey(), store.length);
|
|
while (trial < store.length) {
|
|
int index = probingStrategy.nextIndex(baseIndex, trial, store.length);
|
|
Entry<K,V> entry = (Entry<K,V>) store[index];
|
|
if (entry == null || entry == REMOVED)
|
|
{
|
|
if (!resize)
|
|
{
|
|
linkNewEntry(nEntry);
|
|
size++;
|
|
if (entry != REMOVED) {
|
|
actualSize++;
|
|
}
|
|
}
|
|
|
|
store[index] = nEntry;
|
|
return;
|
|
}
|
|
else if (entry.getKey().equals(nEntry.getKey()))
|
|
{
|
|
if (!resize) {
|
|
linkUpdatedEntry(entry);
|
|
}
|
|
nEntry.next = entry.next;
|
|
nEntry.prev = entry.prev;
|
|
store[index] = nEntry;
|
|
return;
|
|
}
|
|
trial++;
|
|
}
|
|
throw new IllegalStateException("Impossible");
|
|
}
|
|
|
|
private void linkNewEntry(Entry<K, V> nEntry)
|
|
{
|
|
if (head == null)
|
|
{
|
|
head = nEntry;
|
|
}
|
|
else
|
|
{
|
|
tail.next = nEntry;
|
|
nEntry.prev = tail;
|
|
}
|
|
tail = nEntry;
|
|
}
|
|
|
|
private void linkUpdatedEntry(Entry<K, V> entry)
|
|
{
|
|
if (head == null)
|
|
{
|
|
head = entry;
|
|
} else if (entry == head) {
|
|
if (entry.next != null) {
|
|
head = entry.next;
|
|
tail.next = entry;
|
|
entry.prev = tail;
|
|
tail = entry;
|
|
}
|
|
} else if (entry != tail) {
|
|
entry.prev.next = entry.next;
|
|
entry.next.prev = entry.prev;
|
|
tail.next = entry;
|
|
entry.prev = tail;
|
|
entry.next = null;
|
|
tail = entry;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void remove(K key) {
|
|
int trial = 0;
|
|
int baseIndex = calcIndex(key, store.length);
|
|
while (trial < store.length)
|
|
{
|
|
int index = probingStrategy.nextIndex(baseIndex, trial, store.length);
|
|
Entry<K,V> entry = (Entry<K,V>) store[index];
|
|
if (entry == null)
|
|
{
|
|
return;
|
|
}
|
|
else if (entry.getKey().equals(key))
|
|
{
|
|
unlinkEntry(entry);
|
|
store[index] = REMOVED;
|
|
size--;
|
|
return;
|
|
}
|
|
trial++;
|
|
}
|
|
}
|
|
|
|
private void unlinkEntry(Entry<K, V> entry)
|
|
{
|
|
if (head == entry) {
|
|
head = entry.next;
|
|
}
|
|
if (tail == entry) {
|
|
tail = entry.prev;
|
|
}
|
|
if (entry.prev != null) {
|
|
entry.prev.next = entry.next;
|
|
}
|
|
if (entry.next != null) {
|
|
entry.next.prev = entry.prev;
|
|
}
|
|
}
|
|
|
|
public int size() {
|
|
return size;
|
|
}
|
|
|
|
private int calcIndex(K key, int size) {
|
|
return key.hashCode() % size;
|
|
}
|
|
|
|
public Iterator<Map.Entry<K,V>> iterator() {
|
|
return new LinkedHashTableIterator();
|
|
}
|
|
|
|
public String toString() {
|
|
StringBuilder sb = new StringBuilder();
|
|
for (LinkedHashTableIterator iterator = new LinkedHashTableIterator(); iterator.hasNext();)
|
|
{
|
|
Entry<K, V> entry = iterator.next();
|
|
sb.append(entry + ", ");
|
|
}
|
|
|
|
sb.delete(sb.length()-2, sb.length());
|
|
|
|
return "{" + sb + "}";
|
|
}
|
|
|
|
public interface ProbingStrategy {
|
|
int nextIndex(int baseIndex, int probingTrial, int size);
|
|
}
|
|
|
|
public static class LinearProbing implements ProbingStrategy {
|
|
public int nextIndex(int baseIndex, int probingTrial, int size) {
|
|
return (baseIndex + probingTrial) % size;
|
|
}
|
|
}
|
|
|
|
public static class QuadraticProbing implements ProbingStrategy {
|
|
public int nextIndex(int baseIndex, int probingTrial, int size) {
|
|
return (baseIndex + probingTrial * probingTrial) % size;
|
|
}
|
|
}
|
|
|
|
public static class Entry<K, V> extends Map.Entry<K, V> {
|
|
private Entry<K, V> prev;
|
|
private Entry<K, V> next;
|
|
|
|
public Entry(K key, V val) {
|
|
super(key, val);
|
|
}
|
|
}
|
|
|
|
private class HashTableIterator implements Iterator<Entry<K,V>> {
|
|
private int index = 0;
|
|
private int size = store.length;
|
|
|
|
public boolean hasNext() {
|
|
return index < size;
|
|
}
|
|
|
|
public Entry<K,V> next() {
|
|
while (index < size) {
|
|
Entry<K,V> entry = (Entry<K,V>) store[index++];
|
|
if (entry != null && entry != REMOVED) {
|
|
return entry;
|
|
}
|
|
}
|
|
throw new NoSuchElementException();
|
|
}
|
|
}
|
|
|
|
private class LinkedHashTableIterator implements Iterator<Map.Entry<K,V>> {
|
|
private Entry<K,V> start = head;
|
|
|
|
public boolean hasNext() {
|
|
return start != null;
|
|
}
|
|
|
|
public Entry<K,V> next() {
|
|
if (start != null) {
|
|
Entry<K, V> entry = start;
|
|
start = start.next;
|
|
return entry;
|
|
}
|
|
throw new NoSuchElementException();
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args)
|
|
{
|
|
OpenAddressingHashTable<String, String> table =
|
|
new OpenAddressingHashTable<>(2, 0.75, new LinearProbing());
|
|
table.put("a", "abhinav");
|
|
table.put("b", "batman");
|
|
table.put("c", "carol");
|
|
table.put("c", "carly");
|
|
|
|
table.remove("a");
|
|
System.out.println(table.size());
|
|
|
|
table.put("z", "zellman");
|
|
table.put("w", "walker");
|
|
System.out.println(table);
|
|
System.out.println(table.size());
|
|
|
|
table.remove("x");
|
|
table.remove("z");
|
|
System.out.println(table.size());
|
|
|
|
System.out.println(table.get("a"));
|
|
System.out.println(table.get("b"));
|
|
System.out.println(table.get("c"));
|
|
System.out.println(table.get("w"));
|
|
System.out.println(table.get("z"));
|
|
|
|
table.put("z", "zebra");
|
|
System.out.println(table.get("z"));
|
|
System.out.println(table);
|
|
System.out.println(table.size());
|
|
|
|
table.put("w", "wanted");
|
|
System.out.println(table);
|
|
System.out.println(table.size());
|
|
|
|
System.out.println(table.get("A"));
|
|
System.out.println(table.get("B"));
|
|
System.out.println(table.get("C"));
|
|
}
|
|
|
|
}
|