diff --git a/pom.xml b/pom.xml
index 36c092b..df1c82c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,9 +27,15 @@
- junit
- junit
- 4.12
+ org.projectlombok
+ lombok
+ 1.18.16
+ compile
+
+
+ org.junit.jupiter
+ junit-jupiter
+ RELEASE
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..c51a018 100644
--- a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
+++ b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
@@ -1,43 +1,180 @@
package de.comparus.opensource.longmap;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.IntStream;
+
public class LongMapImpl implements LongMap {
+
+ private static final int DEFAULT_CAPACITY = 16;
+
+ private static final double DEFAULT_LOAD_FACTOR = 0.75;
+
+ private final List>> buckets;
+
+ private int capacity;
+
+ private int size;
+
+ private final double loadFactor;
+
+ public LongMapImpl() {
+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
+ }
+
+ public LongMapImpl(int capacity, double loadFactor) {
+ this.capacity = capacity;
+ this.buckets = new ArrayList<>(capacity);
+ this.loadFactor = loadFactor;
+ IntStream.range(0, capacity).forEach(i -> buckets.add(null));
+ }
+
+ @Override
public V put(long key, V value) {
- return null;
+ if (size >= capacity * loadFactor) {
+ resize();
+ }
+
+ int index = getIndex(key);
+ LinkedList> bucket = buckets.get(index);
+
+ if (bucket == null) {
+ bucket = new LinkedList<>();
+ buckets.set(index, bucket);
+ }
+
+ for (Entry entry : bucket) {
+ if (entry.getKey() == key) {
+ entry.setValue(value);
+ return entry.value;
+ }
+ }
+
+ bucket.add(new Entry<>(key, value));
+ size++;
+ return bucket.stream()
+ .filter(entry -> entry.key == key)
+ .findFirst()
+ .map(entry -> entry.value)
+ .orElse(null);
}
+ @Override
public V get(long key) {
- return null;
+ int index = getIndex(key);
+ LinkedList> bucket = buckets.get(index);
+
+ if (bucket == null) {
+ return null;
+ }
+
+ Optional> valueEntry = bucket.stream()
+ .filter(entry -> entry.getKey() == key)
+ .findFirst();
+
+ return valueEntry.map(Entry::getValue).orElse(null);
}
+ @Override
public V remove(long key) {
- return null;
+ int index = getIndex(key);
+ LinkedList> bucket = buckets.get(index);
+
+ if (bucket == null) {
+ return null;
+ }
+
+ V removedValue = null;
+ for (Entry entry : bucket) {
+ if (entry.getKey() == key) {
+ bucket.remove(entry);
+ size--;
+ removedValue = entry.value;
+ }
+ }
+
+ return removedValue;
}
+ @Override
public boolean isEmpty() {
- return false;
+ return size == 0;
}
+ @Override
public boolean containsKey(long key) {
- return false;
+ return get(key) != null;
}
+ @Override
public boolean containsValue(V value) {
- return false;
+ return buckets.stream()
+ .filter(Objects::nonNull)
+ .anyMatch(bucket -> bucket.stream()
+ .anyMatch(entry -> entry.value == value)
+ );
}
+ @Override
public long[] keys() {
- return null;
+ return buckets.stream()
+ .filter(Objects::nonNull)
+ .flatMapToLong(bucket -> bucket.stream().mapToLong(entry -> entry.key))
+ .distinct()
+ .toArray();
}
+ @Override
public V[] values() {
- return null;
+ return (V[]) buckets.stream()
+ .filter(Objects::nonNull)
+ .flatMap(bucket -> bucket.stream().map(entry -> entry.value))
+ .toArray();
}
+ @Override
public long size() {
- return 0;
+ return size;
}
+ public int getCapacity() {
+ return capacity;
+ }
+
+ @Override
public void clear() {
+ IntStream.range(0, buckets.size()).forEach(i -> buckets.set(i, null));
+ size = 0;
+ }
+ private int getIndex(long key) {
+ return Math.abs(Objects.hashCode(key)) % capacity;
}
+
+ private void resize() {
+ int newCapacity = capacity * 2;
+ int capacityDelta = newCapacity - capacity;
+ List>> newReservedBuckets = new ArrayList<>(capacityDelta);
+
+ IntStream.range(0, capacityDelta).forEach(bucket -> newReservedBuckets.add(null));
+ buckets.addAll(newReservedBuckets);
+ capacity = newCapacity;
+ }
+
+ @Data
+ @AllArgsConstructor
+ private class Entry {
+
+ private final long key;
+
+ private T value;
+
+ }
+
}
diff --git a/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java b/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java
new file mode 100644
index 0000000..ead7a7b
--- /dev/null
+++ b/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java
@@ -0,0 +1,177 @@
+package de.comparus.opensource.longmap;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.stream.IntStream;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+class LongMapImplTest {
+
+ private LongMap testMap;
+
+ @BeforeEach
+ void setUp() {
+ testMap = new LongMapImpl<>();
+ }
+
+ @Test
+ void testPut() {
+ // given
+ long[] expectedKeys = {1L, 2L, 3L};
+ String[] expectedValues = {"one", "two", "three"};
+
+ // when
+ IntStream.range(0, expectedKeys.length)
+ .forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
+
+ // then
+ assertEquals(3, testMap.size());
+ assertArrayEquals(expectedKeys, testMap.keys());
+ assertArrayEquals(expectedValues, testMap.values());
+ }
+
+ @Test
+ void testPut_WhenEntryIsPresent() {
+ // given
+ long[] expectedKeys = {1L, 2L, 3L};
+ String[] expectedValues = {"one", "two", "three"};
+
+ // when
+ IntStream.range(0, expectedKeys.length)
+ .forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
+ testMap.put(expectedKeys[1], "twotwo");
+ expectedValues[1] = "twotwo";
+
+ // then
+ assertEquals(3, testMap.size());
+ assertArrayEquals(expectedKeys, testMap.keys());
+ assertArrayEquals(expectedValues, testMap.values());
+ }
+
+ @Test
+ void testGet() {
+ // given
+ long expectedKey = 1L;
+ String expectedValue = "one";
+
+ // when
+ testMap.put(expectedKey, expectedValue);
+ String actualValue = testMap.get(expectedKey);
+
+ // then
+ assertEquals(expectedValue, actualValue);
+ }
+
+ @Test
+ void testGet_WhenEntryIsNotFound() {
+ // when
+ String actualValue = testMap.get(1L);
+
+ // then
+ assertNull(actualValue);
+ }
+
+ @Test
+ void testRemove() {
+ // given
+ long expectedKey = 1L;
+ String expectedValue = "one";
+
+ // when
+ testMap.put(expectedKey, expectedValue);
+ String deletedValue = testMap.remove(expectedKey);
+
+ // then
+ assertEquals(expectedValue, deletedValue);
+ }
+
+ @Test
+ void testRemove_WhenEntryIsNotFound() {
+ // when
+ String deletedValue = testMap.remove(1L);
+
+ // then
+ assertNull(deletedValue);
+ }
+
+ @Test
+ void testIsEmpty() {
+ // given
+ long expectedKey = 1L;
+ String expectedValue = "one";
+
+ // when
+ testMap.put(expectedKey, expectedValue);
+ boolean isEmpty = testMap.isEmpty();
+
+ // then
+ assertFalse(isEmpty);
+ }
+
+ @Test
+ void testContainsKey() {
+ // given
+ long[] expectedKeys = {1L, 2L, 3L};
+ String[] expectedValues = {"one", "two", "three"};
+
+ // when
+ IntStream.range(0, expectedKeys.length)
+ .forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
+ boolean containsKey = testMap.containsKey(expectedKeys[1]);
+
+ // then
+ assertTrue(containsKey);
+ }
+
+ @Test
+ void testContainsValue() {
+ // given
+ long[] expectedKeys = {1L, 2L, 3L};
+ String[] expectedValues = {"one", "two", "three"};
+
+ // when
+ IntStream.range(0, expectedKeys.length)
+ .forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
+ boolean containsValue = testMap.containsValue(expectedValues[1]);
+
+ // then
+ assertTrue(containsValue);
+ }
+
+ @Test
+ void testClear() {
+ // given
+ long[] expectedKeys = {1L, 2L, 3L};
+ String[] expectedValues = {"one", "two", "three"};
+
+ // when
+ IntStream.range(0, expectedKeys.length)
+ .forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
+ boolean nonEmptyBeforeClear = !testMap.isEmpty();
+ testMap.clear();
+ boolean emptyAfterClear = testMap.isEmpty();
+
+ // then
+ assertTrue(nonEmptyBeforeClear);
+ assertTrue(emptyAfterClear);
+ }
+
+ @Test
+ void testResize() {
+ LongMapImpl testResizeMap = new LongMapImpl<>();
+ final int TEST_SIZE = 17;
+ final int EXPECTED_CAPACITY = 32;
+ IntStream.range(0, TEST_SIZE).forEach(i -> testResizeMap.put(++i, "test"));
+
+ assertEquals(TEST_SIZE, testResizeMap.size());
+ assertEquals(EXPECTED_CAPACITY, testResizeMap.getCapacity());
+ }
+
+}
\ No newline at end of file