Fix linear probing hash map (#3902)
This commit is contained in:
parent
45923d6872
commit
b98dc2c5b5
@ -1,203 +0,0 @@
|
||||
package com.thealgorithms.datastructures.hashmap.hashing;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This class is an implementation of a hash table using linear probing It uses
|
||||
* a dynamic array to lengthen the size of the hash table when load factor > .7
|
||||
*/
|
||||
public class HashMapLinearProbing {
|
||||
|
||||
private int hsize; // size of the hash table
|
||||
private Integer[] buckets; // array representing the table
|
||||
private Integer AVAILABLE;
|
||||
private int size; // amount of elements in the hash table
|
||||
|
||||
/**
|
||||
* Constructor initializes buckets array, hsize, and creates dummy object
|
||||
* for AVAILABLE
|
||||
*
|
||||
* @param hsize the desired size of the hash map
|
||||
*/
|
||||
public HashMapLinearProbing(int hsize) {
|
||||
this.buckets = new Integer[hsize];
|
||||
this.hsize = hsize;
|
||||
this.AVAILABLE = Integer.MIN_VALUE;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Hash Function takes a given key and finds an index based on its data
|
||||
*
|
||||
* @param key the desired key to be converted
|
||||
* @return int an index corresponding to the key
|
||||
*/
|
||||
public int hashing(int key) {
|
||||
int hash = key % hsize;
|
||||
if (hash < 0) {
|
||||
hash += hsize;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* inserts the key into the hash map by wrapping it as an Integer object
|
||||
*
|
||||
* @param key the desired key to be inserted in the hash map
|
||||
*/
|
||||
public void insertHash(int key) {
|
||||
Integer wrappedInt = key;
|
||||
int hash = hashing(key);
|
||||
|
||||
if (isFull()) {
|
||||
System.out.println("Hash table is full");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hsize; i++) {
|
||||
if (buckets[hash] == null || buckets[hash] == AVAILABLE) {
|
||||
buckets[hash] = wrappedInt;
|
||||
size++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hash + 1 < hsize) {
|
||||
hash++;
|
||||
} else {
|
||||
hash = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes a key from the hash map and adds an available placeholder
|
||||
*
|
||||
* @param key the desired key to be deleted
|
||||
*/
|
||||
public void deleteHash(int key) {
|
||||
Integer wrappedInt = key;
|
||||
int hash = hashing(key);
|
||||
|
||||
if (isEmpty()) {
|
||||
System.out.println("Table is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hsize; i++) {
|
||||
if (buckets[hash] != null && buckets[hash].equals(wrappedInt)) {
|
||||
buckets[hash] = AVAILABLE;
|
||||
size--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hash + 1 < hsize) {
|
||||
hash++;
|
||||
} else {
|
||||
hash = 0;
|
||||
}
|
||||
}
|
||||
System.out.println("Key " + key + " not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the hash table line by line
|
||||
*/
|
||||
public void displayHashtable() {
|
||||
for (int i = 0; i < hsize; i++) {
|
||||
if (buckets[i] == null || buckets[i] == AVAILABLE) {
|
||||
System.out.println("Bucket " + i + ": Empty");
|
||||
} else {
|
||||
System.out.println(
|
||||
"Bucket " + i + ": " + buckets[i].toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of location based on an inputed key
|
||||
*
|
||||
* @param key the desired key to be found
|
||||
* @return int the index where the key is located
|
||||
*/
|
||||
public int findHash(int key) {
|
||||
Integer wrappedInt = key;
|
||||
int hash = hashing(key);
|
||||
|
||||
if (isEmpty()) {
|
||||
System.out.println("Table is empty");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hsize; i++) {
|
||||
try {
|
||||
if (buckets[hash].equals(wrappedInt)) {
|
||||
buckets[hash] = AVAILABLE;
|
||||
return hash;
|
||||
}
|
||||
} catch (Exception E) {}
|
||||
|
||||
if (hash + 1 < hsize) {
|
||||
hash++;
|
||||
} else {
|
||||
hash = 0;
|
||||
}
|
||||
}
|
||||
System.out.println("Key " + key + " not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void lengthenTable() {
|
||||
buckets = Arrays.copyOf(buckets, hsize * 2);
|
||||
hsize *= 2;
|
||||
System.out.println("Table size is now: " + hsize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the load factor of the hash table if greater than .7,
|
||||
* automatically lengthens table to prevent further collisions
|
||||
*/
|
||||
public void checkLoadFactor() {
|
||||
double factor = (double) size / hsize;
|
||||
if (factor > .7) {
|
||||
System.out.println(
|
||||
"Load factor is " + factor + ", lengthening table"
|
||||
);
|
||||
lengthenTable();
|
||||
} else {
|
||||
System.out.println("Load factor is " + factor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* isFull returns true if the hash map is full and false if not full
|
||||
*
|
||||
* @return boolean is Empty
|
||||
*/
|
||||
public boolean isFull() {
|
||||
boolean response = true;
|
||||
for (int i = 0; i < hsize; i++) {
|
||||
if (buckets[i] == null || buckets[i] == AVAILABLE) {
|
||||
response = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* isEmpty returns true if the hash map is empty and false if not empty
|
||||
*
|
||||
* @return boolean is Empty
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
boolean response = true;
|
||||
for (int i = 0; i < hsize; i++) {
|
||||
if (buckets[i] != null) {
|
||||
response = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package com.thealgorithms.datastructures.hashmap.hashing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/***
|
||||
* This class is an implementation of a hash table using linear probing.
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Linear_probing">Linear Probing Hash Table</a>
|
||||
*
|
||||
* @param <Key> keys type.
|
||||
* @param <Value> values type.
|
||||
*/
|
||||
public class LinearProbingHashMap<Key extends Comparable<Key>, Value> extends Map<Key, Value> {
|
||||
private int hsize; // size of the hash table
|
||||
private Key[] keys;
|
||||
private Value[] values;
|
||||
private int size; // amount of elements in the hash table
|
||||
|
||||
public LinearProbingHashMap() {
|
||||
this(16);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public LinearProbingHashMap(int size) {
|
||||
this.hsize = size;
|
||||
keys = (Key[]) new Comparable[size];
|
||||
values = (Value[]) new Object[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean put(Key key, Value value) {
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (size > hsize / 2) {
|
||||
resize(2 * hsize);
|
||||
}
|
||||
|
||||
int keyIndex = hash(key, hsize);
|
||||
for (; keys[keyIndex] != null; keyIndex = increment(keyIndex)) {
|
||||
if (key.equals(keys[keyIndex])) {
|
||||
values[keyIndex] = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
keys[keyIndex] = key;
|
||||
values[keyIndex] = value;
|
||||
size++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value get(Key key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = hash(key, hsize); keys[i] != null; i = increment(i)) {
|
||||
if (key.equals(keys[i])) {
|
||||
return values[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(Key key) {
|
||||
if (key == null || !contains(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int i = hash(key, hsize);
|
||||
while (!key.equals(keys[i])) {
|
||||
i = increment(i);
|
||||
}
|
||||
|
||||
keys[i] = null;
|
||||
values[i] = null;
|
||||
|
||||
i = increment(i);
|
||||
while (keys[i] != null) {
|
||||
// delete keys[i] an vals[i] and reinsert
|
||||
Key keyToRehash = keys[i];
|
||||
Value valToRehash = values[i];
|
||||
keys[i] = null;
|
||||
values[i] = null;
|
||||
size--;
|
||||
put(keyToRehash, valToRehash);
|
||||
i = increment(i);
|
||||
}
|
||||
|
||||
size--;
|
||||
if (size > 0 && size <= hsize / 8) {
|
||||
resize(hsize / 2);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Key key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
Iterable<Key> keys() {
|
||||
ArrayList<Key> listOfKeys = new ArrayList<>(size);
|
||||
for (int i = 0; i < hsize; i++) {
|
||||
if (keys[i] != null) {
|
||||
listOfKeys.add(keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
listOfKeys.sort(Comparable::compareTo);
|
||||
return listOfKeys;
|
||||
}
|
||||
|
||||
private int increment(int i) {
|
||||
return (i + 1) % hsize;
|
||||
}
|
||||
|
||||
private void resize(int newSize) {
|
||||
LinearProbingHashMap<Key, Value> tmp = new LinearProbingHashMap<>(newSize);
|
||||
for (int i = 0; i < hsize; i++) {
|
||||
if (keys[i] != null) {
|
||||
tmp.put(keys[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.keys = tmp.keys;
|
||||
this.values = tmp.values;
|
||||
this.hsize = newSize;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package com.thealgorithms.datastructures.hashmap.hashing;
|
||||
|
||||
import java.util.Scanner;
|
||||
|
||||
public class MainLinearProbing {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int choice, key;
|
||||
|
||||
HashMapLinearProbing h = new HashMapLinearProbing(7);
|
||||
Scanner In = new Scanner(System.in);
|
||||
|
||||
while (true) {
|
||||
System.out.println("Enter your Choice :");
|
||||
System.out.println("1. Add Key");
|
||||
System.out.println("2. Delete Key");
|
||||
System.out.println("3. Print Table");
|
||||
System.out.println("4. Exit");
|
||||
System.out.println("5. Search and print key index");
|
||||
System.out.println("6. Check load factor");
|
||||
|
||||
choice = In.nextInt();
|
||||
|
||||
switch (choice) {
|
||||
case 1:
|
||||
{
|
||||
System.out.println("Enter the Key: ");
|
||||
key = In.nextInt();
|
||||
h.insertHash(key);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
System.out.println("Enter the Key delete: ");
|
||||
key = In.nextInt();
|
||||
h.deleteHash(key);
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
System.out.println("Print table");
|
||||
h.displayHashtable();
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
In.close();
|
||||
return;
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
System.out.println(
|
||||
"Enter the Key to find and print: "
|
||||
);
|
||||
key = In.nextInt();
|
||||
System.out.println(
|
||||
"Key: " + key + " is at index: " + h.findHash(key)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
h.checkLoadFactor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.thealgorithms.datastructures.hashmap.hashing;
|
||||
|
||||
public abstract class Map<Key, Value> {
|
||||
|
||||
abstract boolean put(Key key, Value value);
|
||||
|
||||
abstract Value get(Key key);
|
||||
|
||||
abstract boolean delete(Key key);
|
||||
|
||||
abstract Iterable<Key> keys();
|
||||
|
||||
abstract int size();
|
||||
|
||||
public boolean contains(Key key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
protected int hash(Key key, int size) {
|
||||
return (key.hashCode() & Integer.MAX_VALUE) % size;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.thealgorithms.datastructures.hashmap.hashing;
|
||||
|
||||
class LinearProbingHashMapTest extends MapTest {
|
||||
@Override
|
||||
<Key extends Comparable<Key>, Value> Map<Key, Value> getMap() {
|
||||
return new LinearProbingHashMap<>();
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package com.thealgorithms.datastructures.hashmap.hashing;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
abstract class MapTest {
|
||||
abstract <Key extends Comparable<Key>, Value> Map<Key, Value> getMap();
|
||||
|
||||
@Test
|
||||
void putTest() {
|
||||
Map<Integer, String> map = getMap();
|
||||
|
||||
assertFalse(map.put(null, "-25"));
|
||||
assertFalse(map.put(null, null));
|
||||
assertTrue(map.put(-25, "-25"));
|
||||
assertTrue(map.put(33, "33"));
|
||||
assertTrue(map.put(100, "100"));
|
||||
assertTrue(map.put(100, "+100"));
|
||||
assertTrue(map.put(100, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTest() {
|
||||
Map<Integer, String> map = getMap();
|
||||
for (int i = -100; i < 100; i++) {
|
||||
map.put(i, String.valueOf(i));
|
||||
}
|
||||
|
||||
for (int i = -100; i < 100; i++) {
|
||||
assertEquals(map.get(i), String.valueOf(i));
|
||||
}
|
||||
|
||||
for (int i = 100; i < 200; i++) {
|
||||
assertNull(map.get(i));
|
||||
}
|
||||
|
||||
assertNull(map.get(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteTest() {
|
||||
Map<Integer, String> map = getMap();
|
||||
for (int i = -100; i < 100; i++) {
|
||||
map.put(i, String.valueOf(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
assertTrue(map.delete(i));
|
||||
}
|
||||
|
||||
for (int i = 100; i < 200; i++) {
|
||||
assertFalse(map.delete(i));
|
||||
}
|
||||
|
||||
assertFalse(map.delete(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsTest() {
|
||||
Map<Integer, String> map = getMap();
|
||||
for (int i = -100; i < 100; i++) {
|
||||
map.put(i, String.valueOf(i));
|
||||
}
|
||||
|
||||
for (int i = -50; i < 50; i++) {
|
||||
assertTrue(map.contains(i));
|
||||
}
|
||||
|
||||
for (int i = 100; i < 200; i++) {
|
||||
assertFalse(map.contains(i));
|
||||
}
|
||||
|
||||
assertFalse(map.contains(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sizeTest() {
|
||||
Map<Integer, String> map = getMap();
|
||||
assertEquals(map.size(), 0);
|
||||
|
||||
for (int i = -100; i < 100; i++) {
|
||||
map.put(i, String.valueOf(i));
|
||||
}
|
||||
|
||||
assertEquals(map.size(), 200);
|
||||
|
||||
for (int i = -50; i < 50; i++) {
|
||||
map.delete(i);
|
||||
}
|
||||
|
||||
assertEquals(map.size(), 100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keysTest() {
|
||||
Map<Integer, String> map = getMap();
|
||||
Iterable<Integer> keys = map.keys();
|
||||
assertFalse(keys.iterator().hasNext());
|
||||
|
||||
for (int i = 100; i > -100; i--) {
|
||||
map.put(i, String.valueOf(i));
|
||||
}
|
||||
|
||||
keys = map.keys();
|
||||
int i = -100;
|
||||
for (Integer key : keys) {
|
||||
assertEquals(key, ++i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashTest() {
|
||||
Map<Integer, String> map = getMap();
|
||||
int testSize = 100;
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
int randomInt = random.nextInt();
|
||||
int hashIndex = map.hash(randomInt, testSize);
|
||||
int negateHashIndex = map.hash(-randomInt, testSize);
|
||||
assertTrue(hashIndex >= 0);
|
||||
assertTrue(hashIndex < testSize);
|
||||
assertTrue(negateHashIndex >= 0);
|
||||
assertTrue(negateHashIndex < testSize);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user