From 01ad191eb819cbfc88b84870546eb4a43090358a Mon Sep 17 00:00:00 2001 From: Sviat Date: Sat, 3 Jun 2023 19:58:47 +0300 Subject: [PATCH 1/3] solution-created --- pom.xml | 6 +- .../CollisionAwareLongBucketIterator.java | 46 +++ .../opensource/longmap/LongMapImpl.java | 164 +++++++++- .../opensource/longmap/LongMapNode.java | 97 ++++++ src/test/java/LongMapImplTest.java | 280 ++++++++++++++++++ src/test/java/TestUtil.java | 16 + src/test/java/TimingExtension.java | 34 +++ 7 files changed, 632 insertions(+), 11 deletions(-) create mode 100644 src/main/java/de/comparus/opensource/longmap/CollisionAwareLongBucketIterator.java create mode 100644 src/main/java/de/comparus/opensource/longmap/LongMapNode.java create mode 100644 src/test/java/LongMapImplTest.java create mode 100644 src/test/java/TestUtil.java create mode 100644 src/test/java/TimingExtension.java diff --git a/pom.xml b/pom.xml index 36c092b..a4f049e 100644 --- a/pom.xml +++ b/pom.xml @@ -27,9 +27,9 @@ - junit - junit - 4.12 + org.junit.jupiter + junit-jupiter + RELEASE test diff --git a/src/main/java/de/comparus/opensource/longmap/CollisionAwareLongBucketIterator.java b/src/main/java/de/comparus/opensource/longmap/CollisionAwareLongBucketIterator.java new file mode 100644 index 0000000..c856ce6 --- /dev/null +++ b/src/main/java/de/comparus/opensource/longmap/CollisionAwareLongBucketIterator.java @@ -0,0 +1,46 @@ +package de.comparus.opensource.longmap; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +class CollisionAwareLongBucketIterator implements Iterator> { + + private final LongMapNode[] buckets; + private LongMapNode next; + private int currentIndex = 0; + + public CollisionAwareLongBucketIterator(LongMapNode[] buckets) { + this.buckets = buckets; + next = getNext(); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public LongMapNode next() { + if (next == null) { + throw new NoSuchElementException("There is no next element to iterate upon."); + } + + LongMapNode current = next; + next = getNext(); + return current; + } + + private LongMapNode getNext() { + if (next != null && next.getCollision() != null) { + return next.getCollision(); + } + + while (currentIndex < buckets.length) { + if (buckets[currentIndex] != null) { + return buckets[currentIndex++]; + } + currentIndex++; + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java index 2f0b54b..878c429 100644 --- a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java +++ b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java @@ -1,43 +1,191 @@ package de.comparus.opensource.longmap; -public class LongMapImpl implements LongMap { +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Iterator; + +public class LongMapImpl implements LongMap, Iterable> { + private static final int ARRAY_MAX_SIZE = Integer.MAX_VALUE - 8; + private static final int DEFAULT_CAPACITY = 8; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + private static final int RESIZE_FACTOR = 2; + final float loadFactor; + private int bucketCount; + private LongMapNode[] buckets; + + public LongMapImpl() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + @SuppressWarnings("unchecked") + public LongMapImpl(int capacity, float loadFactor) { + this.loadFactor = loadFactor; + this.buckets = new LongMapNode[capacity]; + } + public V put(long key, V value) { - return null; + if (bucketCount == ARRAY_MAX_SIZE) { + throw new IllegalStateException(String.format( + "Instance of Map is capped. It can only store %s mappings at max.", ARRAY_MAX_SIZE + )); + } + + if (isThresholdExceeded(key)) { + resize(); + } + + int bucketIndex = getIndex(key); + LongMapNode currentBucket = buckets[bucketIndex]; + + if (currentBucket == null) { + buckets[bucketIndex] = new LongMapNode<>(key, value); + bucketCount++; + return null; + } + + V storedValue = currentBucket.put(key, value); + + if (storedValue == null) { + bucketCount++; + } + return storedValue; } public V get(long key) { - return null; + int index = getIndex(key); + return buckets[index] == null + ? null + : buckets[index].get(key); } public V remove(long key) { + int index = getIndex(key); + LongMapNode currentBucket = buckets[index]; + + if (currentBucket != null) { + if (currentBucket.getKey() == key) { + V result = currentBucket.getValue(); + buckets[index] = currentBucket.getCollision(); + bucketCount--; + return result; + } else { + V removedValue = currentBucket.remove(key); + + if (removedValue != null) { + bucketCount--; + } + + return removedValue; + } + } return null; } public boolean isEmpty() { - return false; + return bucketCount == 0; } public boolean containsKey(long key) { - return false; + return get(key) != null; } public boolean containsValue(V value) { + for (LongMapNode bucket : this) { + if (bucket.getValue() == value || (bucket.getValue() != null && bucket.getValue().equals(value))) { + return true; + } + } return false; } public long[] keys() { - return null; + if (bucketCount == 0) { + return null; + } + long[] keys = new long[bucketCount]; + int keyIndex = bucketCount; + for (LongMapNode bucket : this) { + keys[--keyIndex] = bucket.getKey(); + } + return keys; } + @SuppressWarnings("unchecked") public V[] values() { - return null; + if (bucketCount == 0) { + return null; + } + V[] values = (V[]) Array.newInstance(this.iterator().next().getValue().getClass(), bucketCount); + int valueIndex = bucketCount; + for (LongMapNode bucket : this) { + values[--valueIndex] = bucket.getValue(); + } + return values; } public long size() { - return 0; + return bucketCount; } public void clear() { + Arrays.fill(buckets, null); + bucketCount = 0; + } + + @Override + public Iterator> iterator() { + return new CollisionAwareLongBucketIterator<>(buckets); + } + + @SuppressWarnings("unchecked") + private void resize() { + if (buckets.length == ARRAY_MAX_SIZE) { + return; + } + + LongMapNode[] newBuckets = new LongMapNode[getNewSize()]; + for (LongMapNode currentBucket : buckets) { + while (currentBucket != null) { + int newIndex = getIndex(currentBucket.getKey(), newBuckets); + LongMapNode collision = currentBucket.getCollision(); + currentBucket.setCollision(null); + + LongMapNode rehashedBucket = newBuckets[newIndex]; + + if (rehashedBucket == null) { + newBuckets[newIndex] = currentBucket; + } else { + rehashedBucket.collide(currentBucket); + } + currentBucket = collision; + } + } + this.buckets = newBuckets; + } + + private int getIndex(long key) { + return getIndex(key, buckets); + } + + private int getNewSize() { + if (buckets.length == 0) { + return DEFAULT_CAPACITY; + } + return ARRAY_MAX_SIZE / RESIZE_FACTOR > buckets.length + ? buckets.length * RESIZE_FACTOR + : ARRAY_MAX_SIZE; + } + + private boolean isThresholdExceeded(long key) { + return !containsKey(key) + && (buckets.length == 0 || (int) (buckets.length * loadFactor) <= bucketCount); + } + + private int getIndex(long key, LongMapNode[] storage) { + return (Long.hashCode(key) & 0x7FFFFFFF) % storage.length; + } + public int getCapacity() { + return buckets.length; } } diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapNode.java b/src/main/java/de/comparus/opensource/longmap/LongMapNode.java new file mode 100644 index 0000000..69455ec --- /dev/null +++ b/src/main/java/de/comparus/opensource/longmap/LongMapNode.java @@ -0,0 +1,97 @@ +package de.comparus.opensource.longmap; + +import java.util.Objects; + +class LongMapNode { + + private final long key; + private T value; + private LongMapNode collision; + + public LongMapNode(long key, T value) { + this.key = key; + this.value = value; + } + + public final long getKey() { + return key; + } + + public final T getValue() { + return value; + } + + public final void setValue(T newValue) { + this.value = newValue; + } + + @Override + public final String toString() { + return key + "=" + value; + } + + @Override + public final int hashCode() { + return Objects.hash(key, value); + } + + public LongMapNode getCollision() { + return collision; + } + + public void setCollision(LongMapNode collision) { + this.collision = collision; + } + + public T remove(long key) { + if (collision == null) { + return null; + } + + if (collision.getKey() == key) { + T removed = collision.getValue(); + collision = collision.getCollision(); + return removed; + } else { + return collision.remove(key); + } + } + + public T get(long key) { + if (this.key == key) { + return this.value; + } + if (collision != null) { + return collision.get(key); + } + return null; + } + + public T put(long key, T value) { + if (this.getKey() == key) { + T result = this.getValue(); + this.setValue(value); + return result; + } + + if (collision != null) { + return collision.put(key, value); + } + + collision = new LongMapNode<>(key, value); + return null; + } + + /** + * Convenience method for resizing and rehashing purposes + * + * @param anotherBucket - collision + */ + public void collide(LongMapNode anotherBucket) { + if (collision != null) { + collision.collide(anotherBucket); + } else { + collision = anotherBucket; + } + } + } diff --git a/src/test/java/LongMapImplTest.java b/src/test/java/LongMapImplTest.java new file mode 100644 index 0000000..438fd60 --- /dev/null +++ b/src/test/java/LongMapImplTest.java @@ -0,0 +1,280 @@ +import de.comparus.opensource.longmap.LongMap; +import de.comparus.opensource.longmap.LongMapImpl; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.stream.LongStream; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(TimingExtension.class) +class LongMapImplTest { + private LongMap map; + private static final String PREFIX = "value_"; + private final LongMapImpl instance = new LongMapImpl<>(); + + + @RepeatedTest(10) + public void put_new_then_store_correctly() { + putRandom(100); + + assertEquals(100, instance.size()); + assertEquals(256, instance.getCapacity()); + } + + @Test + public void put_existing_then_update_correctly() { + int mappingsNumber = 100; + long[] keys = putRandom(mappingsNumber); + Arrays.stream(keys) + .forEach(key -> { + String newValue = PREFIX + key; + String oldValue = instance.put(key, newValue); + String updatedValue = instance.get(key); + + assertEquals(PREFIX + key, oldValue); + assertEquals(newValue, updatedValue); + }); + + assertEquals(mappingsNumber, instance.size()); + assertEquals(256, instance.getCapacity()); + } + + @RepeatedTest(10) + public void get_then_return_correctly() { + long[] keys = putRandom(100); + + Arrays.stream(keys) + .forEach(key -> { + String value = instance.get(key); + assertNotNull(value); + assertEquals(PREFIX + key, value); + }); + } + + @Test + public void get_and_map_is_empty_then_return_null() { + assertNull(instance.get(100L)); + } + + @Test + public void get_with_wrong_key_then_return_null() { + put(6); + assertNull(instance.get(7)); + } + + @Test + public void remove_then_delete_correctly() { + int mappingsNumber = 100; + int toBeRemoved = 25; + long[] keys = putRandom(mappingsNumber); + Arrays.stream(keys) + .skip(50) + .limit(toBeRemoved) + .forEach(key -> { + String removed = instance.remove(key); + assertNotNull(removed); + assertEquals(PREFIX + key, removed); + assertNull(instance.get(key)); + }); + + assertEquals(mappingsNumber - toBeRemoved, instance.size()); + } + + @Test + public void remove_with_wrong_key_then_no_delete() { + put(6); + String removed = instance.remove(10); + + assertNull(removed); + assertEquals(6, instance.size()); + } + + @Test + public void empty_then_return_true_otherwise_false() { + assertTrue(instance.isEmpty()); + + put(2); + assertFalse(instance.isEmpty()); + + instance.remove(0); + instance.remove(1); + assertTrue(instance.isEmpty()); + + put(2); + instance.clear(); + assertTrue(instance.isEmpty()); + + put(2); + instance.remove(0); + assertFalse(instance.isEmpty()); + } + + @Test + public void contains_key_then_return_true_otherwise_false() { + long[] keys = putRandom(10); + assertTrue(instance.containsKey(keys[5])); + + instance.remove(keys[5]); + assertFalse(instance.containsKey(keys[5])); + + instance.clear(); + assertFalse(instance.containsKey(keys[5])); + } + + @Test + public void contains_value_then_return_true_otherwise_false() { + long[] keys = putRandom(10); + String valueOne = PREFIX + keys[3]; + String valueTwo = PREFIX + keys[7]; + assertTrue(instance.containsValue(valueOne)); + assertTrue(instance.containsValue(valueTwo)); + assertFalse(instance.containsValue(PREFIX + "wrong")); + + instance.remove(keys[3]); + assertFalse(instance.containsValue(valueOne)); + + instance.clear(); + assertFalse(instance.containsValue(valueTwo)); + } + + @Test + public void contains_null_value_then_return_true() { + instance.put(100L, null); + assertTrue(instance.containsValue(null)); + + instance.put(100L, "val"); + assertFalse(instance.containsValue(null)); + } + + @Test + public void get_keys_then_return_correct_keys_array() { + long[] expectedKeys = Arrays.stream(putRandom(40)).sorted().toArray(); + long[] actualKeys = Arrays.stream(instance.keys()).sorted().toArray(); + assertEquals(instance.size(), actualKeys.length); + assertArrayEquals(expectedKeys, actualKeys); + + instance.remove(expectedKeys[10]); + instance.remove(expectedKeys[25]); + + long[] actualUpdatedKeys = Arrays.stream(instance.keys()).sorted().toArray(); + long[] expectedUpdatedKeys = Arrays.stream(expectedKeys) + .filter(key -> key != expectedKeys[10] && key != expectedKeys[25]) + .sorted() + .toArray(); + assertEquals(instance.size(), expectedUpdatedKeys.length); + assertArrayEquals(expectedUpdatedKeys, actualUpdatedKeys); + + instance.clear(); + long[] emptyKeys = instance.keys(); + assertNull(emptyKeys); + } + + @Test + public void get_values_then_return_correct_value_array() { + int mappingsNumber = 20; + long[] keys = putRandom(mappingsNumber); + + String[] actualValues = Arrays.stream(instance.values()).sorted().toArray(String[]::new); + String[] expectedValues = Arrays.stream(keys) + .mapToObj(key -> PREFIX + key) + .sorted() + .toArray(String[]::new); + assertEquals(instance.size(), actualValues.length); + assertArrayEquals(expectedValues, actualValues); + + instance.remove(keys[11]); + instance.remove(keys[19]); + + String[] actualUpdatedValues = Arrays.stream(instance.values()).sorted().toArray(String[]::new); + String[] expectedUpdatedValues = Arrays.stream(keys) + .filter(key -> key != keys[11] && key != keys[19]) + .mapToObj(key -> PREFIX + key) + .sorted() + .toArray(String[]::new); + assertEquals(actualUpdatedValues.length, expectedUpdatedValues.length); + assertEquals(instance.size(), actualUpdatedValues.length); + assertArrayEquals(expectedUpdatedValues, actualUpdatedValues); + + instance.clear(); + String[] emptyValues = instance.values(); + assertNull(emptyValues); + } + + @Test + public void get_size_return_correct_value() { + assertEquals(0, instance.size()); + + long[] keys = putRandom(100); + assertEquals(100, instance.size()); + + instance.remove(keys[2]); + instance.remove(keys[10]); + instance.remove(keys[59]); + assertEquals(97, instance.size()); + + instance.clear(); + assertEquals(0, instance.size()); + } + + + private void put(long mappingsNumber) { + LongStream + .range(0L, mappingsNumber) + .forEach(key -> instance.put(key, PREFIX + key)); + } + + private long[] putRandom(long mappingsNumber) { + Random random = new Random(); + return LongStream + .range(0L, mappingsNumber) + .map(l -> random.nextLong()) + .peek(key -> instance.put(key, PREFIX + key)) + .toArray(); + } + + @Test + void putMemoryUsage() { + map = new LongMapImpl<>(); + + Runtime runtime = Runtime.getRuntime(); + long memory = runtime.totalMemory() - runtime.freeMemory(); + + fillMap(10000); + long memory1 = runtime.totalMemory() - runtime.freeMemory(); + System.out.println(memory1 - memory); + } + + @Test + void putJavaHashMapMemoryUsage() { + Map example = new HashMap<>(); + Runtime runtime = Runtime.getRuntime(); + long memory = runtime.totalMemory() - runtime.freeMemory(); + + fillJavaMap(10000, example); + long memory1 = runtime.totalMemory() - runtime.freeMemory(); + System.out.println(memory1 - memory); + } + + @Test + void empty_bucket_should_return_null() { + map = new LongMapImpl<>(); + + fillMap(10); + + assertNull(map.get(2473248623L)); + } + + private void fillMap(int amount) { + TestUtil.apply(amount, TestUtil.BOUND, (key, value) -> map.put(key, value)); + } + + private void fillJavaMap(int amount, Map example) { + TestUtil.apply(amount, TestUtil.BOUND, example::put); + } +} \ No newline at end of file diff --git a/src/test/java/TestUtil.java b/src/test/java/TestUtil.java new file mode 100644 index 0000000..2c1bcd5 --- /dev/null +++ b/src/test/java/TestUtil.java @@ -0,0 +1,16 @@ +import java.util.Random; +import java.util.function.BiFunction; + +public class TestUtil { + public static final int BOUND = 32000; + private static final Random RANDOM = new Random(); + + public static void apply(int amount, int bound, BiFunction operator) { + for (int i = 0; i < amount; i++) { + long key = RANDOM.nextLong(); + int value = RANDOM.nextInt(bound); + + operator.apply(key, value); + } + } +} \ No newline at end of file diff --git a/src/test/java/TimingExtension.java b/src/test/java/TimingExtension.java new file mode 100644 index 0000000..b2da010 --- /dev/null +++ b/src/test/java/TimingExtension.java @@ -0,0 +1,34 @@ + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.lang.reflect.Method; +import java.util.logging.Logger; + +public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + private static final Logger LOGGER = Logger.getLogger(TimingExtension.class.getName()); + + private static final String START_TIME = "start time"; + + @Override + public void beforeTestExecution(ExtensionContext context) { + getStore(context).put(START_TIME, System.currentTimeMillis()); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + Method testMethod = context.getRequiredTestMethod(); + long startTime = getStore(context).remove(START_TIME, long.class); + long duration = System.currentTimeMillis() - startTime; + + LOGGER.info(() -> + String.format("Method [%s] took %s ms.", testMethod.getName(), duration)); + } + + private ExtensionContext.Store getStore(ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestMethod())); + } + +} \ No newline at end of file From b1e2aeec8c012d2e2d4ff3b0ebb13b7a972e2fbf Mon Sep 17 00:00:00 2001 From: Sviat Date: Sat, 3 Jun 2023 20:33:57 +0300 Subject: [PATCH 2/3] small-refactor --- src/test/java/LongMapImplTest.java | 164 +++++++++++++++-------------- 1 file changed, 87 insertions(+), 77 deletions(-) diff --git a/src/test/java/LongMapImplTest.java b/src/test/java/LongMapImplTest.java index 438fd60..8ec077b 100644 --- a/src/test/java/LongMapImplTest.java +++ b/src/test/java/LongMapImplTest.java @@ -8,23 +8,28 @@ import java.util.HashMap; import java.util.Map; import java.util.Random; +import java.util.logging.Logger; import java.util.stream.LongStream; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(TimingExtension.class) class LongMapImplTest { - private LongMap map; + private static final Logger LOGGER = Logger.getLogger(TimingExtension.class.getName()); private static final String PREFIX = "value_"; - private final LongMapImpl instance = new LongMapImpl<>(); + private final LongMapImpl stringLongMap = new LongMapImpl<>(); + private LongMap integerLongMap; @RepeatedTest(10) public void put_new_then_store_correctly() { putRandom(100); - assertEquals(100, instance.size()); - assertEquals(256, instance.getCapacity()); + int expectedSize = 100; + assertEquals(expectedSize, stringLongMap.size()); + + int expectedCapacity = 256; + assertEquals(expectedCapacity, stringLongMap.getCapacity()); } @Test @@ -34,15 +39,17 @@ public void put_existing_then_update_correctly() { Arrays.stream(keys) .forEach(key -> { String newValue = PREFIX + key; - String oldValue = instance.put(key, newValue); - String updatedValue = instance.get(key); + String oldValue = stringLongMap.put(key, newValue); + String updatedValue = stringLongMap.get(key); assertEquals(PREFIX + key, oldValue); assertEquals(newValue, updatedValue); }); - assertEquals(mappingsNumber, instance.size()); - assertEquals(256, instance.getCapacity()); + assertEquals(mappingsNumber, stringLongMap.size()); + + int expectedCapacity = 256; + assertEquals(expectedCapacity, stringLongMap.getCapacity()); } @RepeatedTest(10) @@ -51,7 +58,7 @@ public void get_then_return_correctly() { Arrays.stream(keys) .forEach(key -> { - String value = instance.get(key); + String value = stringLongMap.get(key); assertNotNull(value); assertEquals(PREFIX + key, value); }); @@ -59,13 +66,13 @@ public void get_then_return_correctly() { @Test public void get_and_map_is_empty_then_return_null() { - assertNull(instance.get(100L)); + assertNull(stringLongMap.get(100L)); } @Test public void get_with_wrong_key_then_return_null() { put(6); - assertNull(instance.get(7)); + assertNull(stringLongMap.get(7)); } @Test @@ -77,54 +84,54 @@ public void remove_then_delete_correctly() { .skip(50) .limit(toBeRemoved) .forEach(key -> { - String removed = instance.remove(key); + String removed = stringLongMap.remove(key); assertNotNull(removed); assertEquals(PREFIX + key, removed); - assertNull(instance.get(key)); + assertNull(stringLongMap.get(key)); }); - assertEquals(mappingsNumber - toBeRemoved, instance.size()); + assertEquals(mappingsNumber - toBeRemoved, stringLongMap.size()); } @Test public void remove_with_wrong_key_then_no_delete() { put(6); - String removed = instance.remove(10); + String removed = stringLongMap.remove(10); assertNull(removed); - assertEquals(6, instance.size()); + assertEquals(6, stringLongMap.size()); } @Test public void empty_then_return_true_otherwise_false() { - assertTrue(instance.isEmpty()); + assertTrue(stringLongMap.isEmpty()); put(2); - assertFalse(instance.isEmpty()); + assertFalse(stringLongMap.isEmpty()); - instance.remove(0); - instance.remove(1); - assertTrue(instance.isEmpty()); + stringLongMap.remove(0); + stringLongMap.remove(1); + assertTrue(stringLongMap.isEmpty()); put(2); - instance.clear(); - assertTrue(instance.isEmpty()); + stringLongMap.clear(); + assertTrue(stringLongMap.isEmpty()); put(2); - instance.remove(0); - assertFalse(instance.isEmpty()); + stringLongMap.remove(0); + assertFalse(stringLongMap.isEmpty()); } @Test public void contains_key_then_return_true_otherwise_false() { long[] keys = putRandom(10); - assertTrue(instance.containsKey(keys[5])); + assertTrue(stringLongMap.containsKey(keys[5])); - instance.remove(keys[5]); - assertFalse(instance.containsKey(keys[5])); + stringLongMap.remove(keys[5]); + assertFalse(stringLongMap.containsKey(keys[5])); - instance.clear(); - assertFalse(instance.containsKey(keys[5])); + stringLongMap.clear(); + assertFalse(stringLongMap.containsKey(keys[5])); } @Test @@ -132,46 +139,46 @@ public void contains_value_then_return_true_otherwise_false() { long[] keys = putRandom(10); String valueOne = PREFIX + keys[3]; String valueTwo = PREFIX + keys[7]; - assertTrue(instance.containsValue(valueOne)); - assertTrue(instance.containsValue(valueTwo)); - assertFalse(instance.containsValue(PREFIX + "wrong")); + assertTrue(stringLongMap.containsValue(valueOne)); + assertTrue(stringLongMap.containsValue(valueTwo)); + assertFalse(stringLongMap.containsValue(PREFIX + "wrong")); - instance.remove(keys[3]); - assertFalse(instance.containsValue(valueOne)); + stringLongMap.remove(keys[3]); + assertFalse(stringLongMap.containsValue(valueOne)); - instance.clear(); - assertFalse(instance.containsValue(valueTwo)); + stringLongMap.clear(); + assertFalse(stringLongMap.containsValue(valueTwo)); } @Test public void contains_null_value_then_return_true() { - instance.put(100L, null); - assertTrue(instance.containsValue(null)); + stringLongMap.put(100L, null); + assertTrue(stringLongMap.containsValue(null)); - instance.put(100L, "val"); - assertFalse(instance.containsValue(null)); + stringLongMap.put(100L, "val"); + assertFalse(stringLongMap.containsValue(null)); } @Test public void get_keys_then_return_correct_keys_array() { long[] expectedKeys = Arrays.stream(putRandom(40)).sorted().toArray(); - long[] actualKeys = Arrays.stream(instance.keys()).sorted().toArray(); - assertEquals(instance.size(), actualKeys.length); + long[] actualKeys = Arrays.stream(stringLongMap.keys()).sorted().toArray(); + assertEquals(stringLongMap.size(), actualKeys.length); assertArrayEquals(expectedKeys, actualKeys); - instance.remove(expectedKeys[10]); - instance.remove(expectedKeys[25]); + stringLongMap.remove(expectedKeys[10]); + stringLongMap.remove(expectedKeys[25]); - long[] actualUpdatedKeys = Arrays.stream(instance.keys()).sorted().toArray(); + long[] actualUpdatedKeys = Arrays.stream(stringLongMap.keys()).sorted().toArray(); long[] expectedUpdatedKeys = Arrays.stream(expectedKeys) .filter(key -> key != expectedKeys[10] && key != expectedKeys[25]) .sorted() .toArray(); - assertEquals(instance.size(), expectedUpdatedKeys.length); + assertEquals(stringLongMap.size(), expectedUpdatedKeys.length); assertArrayEquals(expectedUpdatedKeys, actualUpdatedKeys); - instance.clear(); - long[] emptyKeys = instance.keys(); + stringLongMap.clear(); + long[] emptyKeys = stringLongMap.keys(); assertNull(emptyKeys); } @@ -180,53 +187,53 @@ public void get_values_then_return_correct_value_array() { int mappingsNumber = 20; long[] keys = putRandom(mappingsNumber); - String[] actualValues = Arrays.stream(instance.values()).sorted().toArray(String[]::new); + String[] actualValues = Arrays.stream(stringLongMap.values()).sorted().toArray(String[]::new); String[] expectedValues = Arrays.stream(keys) .mapToObj(key -> PREFIX + key) .sorted() .toArray(String[]::new); - assertEquals(instance.size(), actualValues.length); + assertEquals(stringLongMap.size(), actualValues.length); assertArrayEquals(expectedValues, actualValues); - instance.remove(keys[11]); - instance.remove(keys[19]); + stringLongMap.remove(keys[11]); + stringLongMap.remove(keys[19]); - String[] actualUpdatedValues = Arrays.stream(instance.values()).sorted().toArray(String[]::new); + String[] actualUpdatedValues = Arrays.stream(stringLongMap.values()).sorted().toArray(String[]::new); String[] expectedUpdatedValues = Arrays.stream(keys) .filter(key -> key != keys[11] && key != keys[19]) .mapToObj(key -> PREFIX + key) .sorted() .toArray(String[]::new); assertEquals(actualUpdatedValues.length, expectedUpdatedValues.length); - assertEquals(instance.size(), actualUpdatedValues.length); + assertEquals(stringLongMap.size(), actualUpdatedValues.length); assertArrayEquals(expectedUpdatedValues, actualUpdatedValues); - instance.clear(); - String[] emptyValues = instance.values(); + stringLongMap.clear(); + String[] emptyValues = stringLongMap.values(); assertNull(emptyValues); } @Test public void get_size_return_correct_value() { - assertEquals(0, instance.size()); + assertEquals(0, stringLongMap.size()); long[] keys = putRandom(100); - assertEquals(100, instance.size()); + assertEquals(100, stringLongMap.size()); - instance.remove(keys[2]); - instance.remove(keys[10]); - instance.remove(keys[59]); - assertEquals(97, instance.size()); + stringLongMap.remove(keys[2]); + stringLongMap.remove(keys[10]); + stringLongMap.remove(keys[59]); + assertEquals(97, stringLongMap.size()); - instance.clear(); - assertEquals(0, instance.size()); + stringLongMap.clear(); + assertEquals(0, stringLongMap.size()); } private void put(long mappingsNumber) { LongStream .range(0L, mappingsNumber) - .forEach(key -> instance.put(key, PREFIX + key)); + .forEach(key -> stringLongMap.put(key, PREFIX + key)); } private long[] putRandom(long mappingsNumber) { @@ -234,44 +241,47 @@ private long[] putRandom(long mappingsNumber) { return LongStream .range(0L, mappingsNumber) .map(l -> random.nextLong()) - .peek(key -> instance.put(key, PREFIX + key)) + .peek(key -> stringLongMap.put(key, PREFIX + key)) .toArray(); } @Test void putMemoryUsage() { - map = new LongMapImpl<>(); + integerLongMap = new LongMapImpl<>(); Runtime runtime = Runtime.getRuntime(); - long memory = runtime.totalMemory() - runtime.freeMemory(); + long memoryBeforeFillingMap = runtime.totalMemory() - runtime.freeMemory(); fillMap(10000); - long memory1 = runtime.totalMemory() - runtime.freeMemory(); - System.out.println(memory1 - memory); + + long memoryAfterFillingMap = runtime.totalMemory() - runtime.freeMemory(); + LOGGER.info(() -> + String.format("Filling LongMap took %s bytes", memoryBeforeFillingMap - memoryAfterFillingMap)); } @Test void putJavaHashMapMemoryUsage() { Map example = new HashMap<>(); Runtime runtime = Runtime.getRuntime(); - long memory = runtime.totalMemory() - runtime.freeMemory(); + long memoryBeforeFillingMap = runtime.totalMemory() - runtime.freeMemory(); fillJavaMap(10000, example); - long memory1 = runtime.totalMemory() - runtime.freeMemory(); - System.out.println(memory1 - memory); + long memoryAfterFillingMap = runtime.totalMemory() - runtime.freeMemory(); + LOGGER.info(() -> + String.format("Filling HashMap took %s bytes", memoryBeforeFillingMap - memoryAfterFillingMap)); } @Test void empty_bucket_should_return_null() { - map = new LongMapImpl<>(); + integerLongMap = new LongMapImpl<>(); fillMap(10); - assertNull(map.get(2473248623L)); + assertNull(integerLongMap.get(2473248623L)); } private void fillMap(int amount) { - TestUtil.apply(amount, TestUtil.BOUND, (key, value) -> map.put(key, value)); + TestUtil.apply(amount, TestUtil.BOUND, (key, value) -> integerLongMap.put(key, value)); } private void fillJavaMap(int amount, Map example) { From 2e138b959269c71c4bedbd2035a7a2b50d41c009 Mon Sep 17 00:00:00 2001 From: Sviat Date: Sat, 3 Jun 2023 21:39:40 +0300 Subject: [PATCH 3/3] small-refactor --- .../opensource/longmap/LongMapNode.java | 139 +++++++++--------- 1 file changed, 67 insertions(+), 72 deletions(-) diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapNode.java b/src/main/java/de/comparus/opensource/longmap/LongMapNode.java index 69455ec..74f6c70 100644 --- a/src/main/java/de/comparus/opensource/longmap/LongMapNode.java +++ b/src/main/java/de/comparus/opensource/longmap/LongMapNode.java @@ -4,94 +4,89 @@ class LongMapNode { - private final long key; - private T value; - private LongMapNode collision; + private final long key; + private T value; + private LongMapNode collision; - public LongMapNode(long key, T value) { - this.key = key; - this.value = value; - } + public LongMapNode(long key, T value) { + this.key = key; + this.value = value; + } - public final long getKey() { - return key; - } + public final long getKey() { + return key; + } - public final T getValue() { - return value; - } + public final T getValue() { + return value; + } - public final void setValue(T newValue) { - this.value = newValue; - } + public final void setValue(T newValue) { + this.value = newValue; + } - @Override - public final String toString() { - return key + "=" + value; - } + @Override + public final String toString() { + return key + "=" + value; + } - @Override - public final int hashCode() { - return Objects.hash(key, value); - } + @Override + public final int hashCode() { + return Objects.hash(key, value); + } - public LongMapNode getCollision() { - return collision; - } + public LongMapNode getCollision() { + return collision; + } - public void setCollision(LongMapNode collision) { - this.collision = collision; - } + public void setCollision(LongMapNode collision) { + this.collision = collision; + } - public T remove(long key) { - if (collision == null) { - return null; - } - - if (collision.getKey() == key) { - T removed = collision.getValue(); - collision = collision.getCollision(); - return removed; - } else { - return collision.remove(key); - } + public T remove(long key) { + if (collision == null) { + return null; } - public T get(long key) { - if (this.key == key) { - return this.value; - } - if (collision != null) { - return collision.get(key); - } - return null; + if (collision.getKey() == key) { + T removed = collision.getValue(); + collision = collision.getCollision(); + return removed; + } else { + return collision.remove(key); } + } - public T put(long key, T value) { - if (this.getKey() == key) { - T result = this.getValue(); - this.setValue(value); - return result; - } + public T get(long key) { + if (this.key == key) { + return this.value; + } + if (collision != null) { + return collision.get(key); + } + return null; + } - if (collision != null) { - return collision.put(key, value); - } + public T put(long key, T value) { + if (this.getKey() == key) { + T result = this.getValue(); + this.setValue(value); + return result; + } - collision = new LongMapNode<>(key, value); - return null; + if (collision != null) { + return collision.put(key, value); } - /** - * Convenience method for resizing and rehashing purposes - * - * @param anotherBucket - collision - */ - public void collide(LongMapNode anotherBucket) { - if (collision != null) { - collision.collide(anotherBucket); - } else { - collision = anotherBucket; - } + collision = new LongMapNode<>(key, value); + return null; + } + + public void collide(LongMapNode anotherBucket) { + if (collision != null) { + collision.collide(anotherBucket); + } else { + collision = anotherBucket; } } +}