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)); + } + } +}