diff --git a/pom.xml b/pom.xml
index 36c092b..345df07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,8 +18,8 @@
3.3
UTF-8
- 1.8
- 1.8
+ 11
+ 11
@@ -27,10 +27,17 @@
- junit
- junit
- 4.12
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.9.2
test
+
+ org.assertj
+ assertj-core
+ 3.23.1
+ test
+
+
diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
index 2f0b54b..ff0caac 100644
--- a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
+++ b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
@@ -1,43 +1,234 @@
package de.comparus.opensource.longmap;
+import java.lang.reflect.Array;
+
public class LongMapImpl implements LongMap {
- public V put(long key, V value) {
- return null;
+
+ private static final int DEFAULT_CAPACITY = 16;
+ private static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ private int capacity;
+ private float loadFactor;
+ private int size;
+ public Entry[] buckets;
+
+ Class type;
+
+ public LongMapImpl(Class type) {
+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, type);
+ }
+
+ public LongMapImpl(int capacity, float loadFactor, Class type) {
+ if (capacity <= 0) {
+ throw new IllegalArgumentException("Invalid capacity: " + capacity);
}
+ if (loadFactor <= 0 || loadFactor > 1) {
+ throw new IllegalArgumentException("Invalid load factor: " + loadFactor);
+ }
+
+ this.type = type;
+
+ this.capacity = capacity;
+ this.loadFactor = loadFactor;
+ this.size = 0;
+ this.buckets = new Entry[capacity];
+ }
+
+ public V put(long key, V value) {
+ int bucketIndex = getBucketIndex(key, capacity);
+ Entry bucket = buckets[bucketIndex];
+
+ if (bucket == null) {
+ buckets[bucketIndex] = new Entry(key, value);
+ } else {
+ Entry prev = null;
+ while (bucket != null) {
+ if (bucket.getKey() == key) {
+ V oldValue = bucket.getValue();
+ bucket.setValue(value);
+ return oldValue;
+ }
+ prev = bucket;
+ bucket = bucket.next;
+ }
- public V get(long key) {
- return null;
+ prev.next = new Entry(key, value);
}
- public V remove(long key) {
- return null;
+ size++;
+
+ if (size > capacity * loadFactor) {
+ resize();
}
- public boolean isEmpty() {
- return false;
+ return null;
+ }
+
+ public V get(long key) {
+ int bucketIndex = getBucketIndex(key, capacity);
+ Entry bucket = buckets[bucketIndex];
+
+ if (bucket != null) {
+ while (bucket != null) {
+ if (bucket.getKey() == key) {
+ return bucket.getValue();
+ }
+ bucket = bucket.next;
+ }
}
- public boolean containsKey(long key) {
- return false;
+ return null;
+ }
+
+ public V remove(long key) {
+ int bucketIndex = getBucketIndex(key, capacity);
+ Entry bucket = buckets[bucketIndex];
+
+ if (bucket != null) {
+ Entry prev = null;
+ while (bucket != null) {
+ if (bucket.getKey() == key) {
+ V value = bucket.getValue();
+ if (prev == null) {
+ if (bucket.next != null) {
+ buckets[bucketIndex] = bucket.next;
+ } else {
+ buckets[bucketIndex] = null;
+ }
+ } else {
+ prev.next = null;
+ }
+ size--;
+ return value;
+ }
+ prev = bucket;
+ bucket = bucket.next;
+ }
}
- public boolean containsValue(V value) {
- return false;
+ return null;
+ }
+
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ public boolean containsKey(long key) {
+ int bucketIndex = getBucketIndex(key, capacity);
+ Entry bucket = buckets[bucketIndex];
+
+ if (bucket != null) {
+ while (bucket != null) {
+ if (bucket.getKey() == key) {
+ return true;
+ }
+ bucket = bucket.next;
+ }
}
+ return false;
+ }
- public long[] keys() {
- return null;
+ public boolean containsValue(V value) {
+ for (Entry bucket : buckets) {
+ while (bucket != null) {
+ if (bucket.getValue().equals(value)) {
+ return true;
+ }
+ bucket = bucket.next;
+ }
}
- public V[] values() {
- return null;
+ return false;
+ }
+
+ public long[] keys() {
+ long[] keys = new long[size];
+ int index = 0;
+
+ for (Entry bucket : buckets) {
+ while (bucket != null) {
+ keys[index++] = bucket.getKey();
+ bucket = bucket.next;
+ }
}
- public long size() {
- return 0;
+ return keys;
+ }
+
+ public V[] values() {
+ V[] values = (V[]) Array.newInstance(type, size);
+ int index = 0;
+
+ for (Entry bucket : buckets) {
+ while (bucket != null) {
+ values[index++] = bucket.getValue();
+ bucket = bucket.next;
+ }
+ }
+
+ return values;
+ }
+
+ public long size() {
+ return size;
+ }
+
+ public void clear() {
+ size = 0;
+ buckets = new Entry[capacity];
+ }
+
+
+ private int getBucketIndex(long key, int capacity) {
+ return (int) Math.abs(key) % capacity;
+ }
+
+ private void resize() {
+ int newCapacity = capacity + capacity/2;
+ if (newCapacity < 0) {
+ throw new IllegalArgumentException("Couldn't be resized. Maximum size has been reached.");
+ }
+ Entry[] newBuckets = new Entry[newCapacity];
+
+ for (Entry bucket : buckets) {
+ while (bucket != null) {
+ int newBucketIndex = getBucketIndex(bucket.getKey(), newCapacity);
+ Entry newBucket = newBuckets[newBucketIndex];
+ if (newBucket == null) {
+ newBuckets[newBucketIndex] = bucket;
+ } else {
+ newBucket.next = bucket;
+ }
+ bucket = bucket.next;
+ }
+ }
+
+ capacity = newCapacity;
+ buckets = newBuckets;
+ }
+
+ private static class Entry {
+
+ private long key;
+ private V value;
+
+ private Entry next;
+
+ public Entry(long key, V value) {
+ this.key = key;
+ this.value = value;
}
- public void clear() {
+ public long getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+ public void setValue(V value) {
+ this.value = value;
}
+ }
}
diff --git a/src/main/test/java/de/comparus/opensource/longmap/LongMapTest.java b/src/main/test/java/de/comparus/opensource/longmap/LongMapTest.java
new file mode 100644
index 0000000..1990359
--- /dev/null
+++ b/src/main/test/java/de/comparus/opensource/longmap/LongMapTest.java
@@ -0,0 +1,92 @@
+package de.comparus.opensource.longmap;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Tag("Unit")
+public class LongMapTest {
+
+ public static final long GENERATED_AMOUNT = 10;
+
+ @Test
+ @DisplayName("Complex test of get, contains key/value, put, resize methods")
+ public void longMapTest() {
+ final long TESTED_AMOUNT_OF_ENTRIES = 10000;
+ LongMap map = generateMapWithValues(TESTED_AMOUNT_OF_ENTRIES);
+ assertMapRange(map, -TESTED_AMOUNT_OF_ENTRIES, TESTED_AMOUNT_OF_ENTRIES);
+ }
+
+ @Test
+ public void removeTest() {
+ LongMap map = generateMapWithValues(GENERATED_AMOUNT);
+ for(long i = 1; i <= GENERATED_AMOUNT; i++) {
+ map.remove(i);
+ }
+ assertEquals(map.size(), 11);
+ assertMapRange(map, -GENERATED_AMOUNT, 0);
+ }
+
+ @Test
+ @DisplayName("Complex test of clear, isEmpty methods")
+ public void clearTest() {
+ LongMap map = generateMapWithValues(GENERATED_AMOUNT);
+ map.clear();
+ assertAll(
+ () -> assertEquals(map.size(), map.values().length),
+ () -> assertTrue(map.isEmpty())
+ );
+ }
+
+ @Test
+ public void valuesTest() {
+ LongMap map = generateMapWithValues(GENERATED_AMOUNT);
+ Long [] valuesOfMap = map.values();
+ Long [] expectedValuesOfMap = Stream
+ .iterate(-GENERATED_AMOUNT, i -> i+1)
+ .limit(20)
+ .toArray(Long[]::new);
+ assertAll(
+ () -> assertEquals( 20, valuesOfMap.length),
+ () -> assertThat(valuesOfMap).containsOnly(expectedValuesOfMap)
+ );
+ }
+
+ @Test
+ public void keysTest() {
+ LongMap map = generateMapWithValues(GENERATED_AMOUNT);
+ long [] keysOfMap = map.keys();
+ long [] expectedKeysOfMap = LongStream
+ .iterate(-GENERATED_AMOUNT, i -> i+1)
+ .limit(20)
+ .toArray();
+ assertAll(
+ () -> assertEquals( 20, keysOfMap.length),
+ () -> assertThat(keysOfMap).containsOnly(expectedKeysOfMap)
+ );
+ }
+
+ private LongMap generateMapWithValues(long TESTED_AMOUNT_OF_ENTRIES) {
+ LongMap map = new LongMapImpl<>(Long.class);
+ for (long i = -TESTED_AMOUNT_OF_ENTRIES; i < TESTED_AMOUNT_OF_ENTRIES; i++) {
+ map.put(i, i);
+ }
+ return map;
+ }
+
+ private static void assertMapRange(LongMap map, long minBound, long maxBound) {
+ for (long i = minBound; i < maxBound; i++) {
+ assertEquals(i, map.get(i));
+ assertTrue(map.containsKey(i));
+ assertTrue(map.containsValue(i));
+ }
+ }
+}