Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Enhance docs, add tests in LinearProbingHashMap #5977

Merged
merged 2 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,51 @@

import java.util.ArrayList;

/***
* This class is an implementation of a hash table using linear probing.
/**
* This class implements a hash table using linear probing to resolve collisions.
* Linear probing is a collision resolution method where each slot in the hash table is checked in a sequential manner
* until an empty slot is found.
*
* <p>
* The class allows for storing key-value pairs, where both the key and value are generic types.
* The key must be of a type that implements the Comparable interface to ensure that the keys can be compared for sorting.
* </p>
*
* <p>
* This implementation supports basic operations such as:
* <ul>
* <li><b>put(Key key, Value value)</b>: Adds a key-value pair to the hash table. If the key already exists, its value is updated.</li>
* <li><b>get(Key key)</b>: Retrieves the value associated with the given key.</li>
* <li><b>delete(Key key)</b>: Removes the key and its associated value from the hash table.</li>
* <li><b>contains(Key key)</b>: Checks if the hash table contains a given key.</li>
* <li><b>size()</b>: Returns the number of key-value pairs in the hash table.</li>
* <li><b>keys()</b>: Returns an iterable collection of keys stored in the hash table.</li>
* </ul>
* </p>
*
* <p>
* The internal size of the hash table is automatically resized when the load factor exceeds 0.5 or falls below 0.125,
* ensuring efficient space utilization.
* </p>
*
* @see <a href="https://en.wikipedia.org/wiki/Linear_probing">Linear Probing Hash Table</a>
*
* @param <Key> keys type.
* @param <Value> values type.
* @param <Key> the type of keys maintained by this map
* @param <Value> the type of mapped values
*/
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
private Key[] keys; // array to store keys
private Value[] values; // array to store values
private int size; // number of elements in the hash table

// Default constructor initializes the table with a default size of 16
public LinearProbingHashMap() {
this(16);
}

@SuppressWarnings("unchecked")
// Constructor to initialize the hash table with a specified size
public LinearProbingHashMap(int size) {
this.hsize = size;
keys = (Key[]) new Comparable[size];
Expand Down Expand Up @@ -81,7 +108,7 @@ public boolean delete(Key key) {

i = increment(i);
while (keys[i] != null) {
// delete keys[i] an vals[i] and reinsert
// Save the key and value for rehashing
Key keyToRehash = keys[i];
Value valToRehash = values[i];
keys[i] = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,91 @@
package com.thealgorithms.datastructures.hashmap.hashing;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

class LinearProbingHashMapTest extends MapTest {

@Override
<Key extends Comparable<Key>, Value> Map<Key, Value> getMap() {
return new LinearProbingHashMap<>();
}

@Test
void putNullKey() {
Map<Integer, String> map = getMap();
assertFalse(map.put(null, "value"), "Putting a null key should return false");
}

@Test
void putDuplicateKeys() {
Map<Integer, String> map = getMap();
map.put(1, "one");
map.put(1, "uno");
assertEquals("uno", map.get(1), "Value should be updated to 'uno'");
}

@Test
void putResizeTest() {
Map<Integer, String> map = getMap();
for (int i = 0; i < 20; i++) {
map.put(i, String.valueOf(i));
}
assertEquals(20, map.size(), "Map size should be 20 after inserting 20 elements");
}

@Test
void deleteNonExistentKey() {
Map<Integer, String> map = getMap();
assertFalse(map.delete(999), "Deleting a non-existent key should return false");
}

@Test
void deleteAndReinsert() {
Map<Integer, String> map = getMap();
map.put(1, "one");
map.delete(1);
assertFalse(map.contains(1), "Map should not contain the deleted key");
map.put(1, "one again");
assertTrue(map.contains(1), "Map should contain the key after reinsertion");
}

@Test
void resizeDown() {
Map<Integer, String> map = getMap();
for (int i = 0; i < 16; i++) {
map.put(i, String.valueOf(i));
}
for (int i = 0; i < 12; i++) {
map.delete(i);
}
assertEquals(4, map.size(), "Map size should be 4 after deleting 12 elements");
}

@Test
void keysOrderTest() {
Map<Integer, String> map = getMap();
for (int i = 10; i > 0; i--) {
map.put(i, String.valueOf(i));
}
int expectedKey = 1;
for (Integer key : map.keys()) {
assertEquals(expectedKey++, key, "Keys should be in sorted order");
}
}

@Test
void stressTest() {
Map<Integer, String> map = getMap();
for (int i = 0; i < 1000; i++) {
map.put(i, String.valueOf(i));
assertEquals(i + 1, map.size(), "Size should match number of inserted elements");
}
for (int i = 0; i < 500; i++) {
map.delete(i);
assertEquals(1000 - (i + 1), map.size(), "Size should decrease correctly");
}
}
}