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