From 625bb6b9d57d454f9869467992dc6f0c5d01f475 Mon Sep 17 00:00:00 2001 From: Severyn Zabara Date: Mon, 10 Jul 2023 18:03:15 +0300 Subject: [PATCH 1/2] Implemented functionality of LongMapImpl and added tests --- .classpath | 42 +++ .project | 23 ++ .settings/org.eclipse.core.resources.prefs | 2 + .settings/org.eclipse.jdt.core.prefs | 8 + .../opensource/longmap/LongMapImpl.java | 279 ++++++++++++++++-- .../opensource/longmap/LongMapImplTest.java | 149 ++++++++++ 6 files changed, 474 insertions(+), 29 deletions(-) create mode 100644 .classpath create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..3c76a92 --- /dev/null +++ b/.classpath @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..231fa24 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + long-map + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..1f887b6 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..cb635b1 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java index 2f0b54b..309656c 100644 --- a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java +++ b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java @@ -1,43 +1,264 @@ package de.comparus.opensource.longmap; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public class LongMapImpl implements LongMap { - public V put(long key, V value) { - return null; - } - public V get(long key) { - 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; + private Entry[] buckets; + long[] keysCache = null; + V[] valuesCache = null; + + Class type = (Class) Object.class; + + public LongMapImpl() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, null); + } + + 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( + "Capacity can not be less zero." + System.lineSeparator() + "Invalid capacity: " + capacity); + } + if (loadFactor <= 0 || loadFactor > 1) { + throw new IllegalArgumentException("The value of the load factor can only be in the range of 0 to 1" + + System.lineSeparator() + "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) { + keysCache = null; + valuesCache = null; + if (type == null || type == Object.class) { + type = (Class) value.getClass(); + } else if (value.getClass() != type) { + throw new IllegalArgumentException( + "Invalid value type. Available only " + type.getName() + "values for inserting"); + } + int bucketIndex = getBucketIndex(key, capacity); + Entry bucketEntry = buckets[bucketIndex]; + if (bucketEntry != null) { + Entry lastNotNullEntry = null; + while (bucketEntry != null) { + if (bucketEntry.getKey() == key) { + V prevValue = bucketEntry.getValue(); + lastNotNullEntry.next = bucketEntry; + bucketEntry.setValue(value); + return prevValue; + } + lastNotNullEntry = bucketEntry; + bucketEntry = bucketEntry.next; + + } + lastNotNullEntry.next = new Entry(key, value); + + } else { + buckets[bucketIndex] = new Entry(key, value); + } + size++; + + if (size > capacity * loadFactor) { + resize(); + } + return null; + } + + public V get(long key) { + int bucketIndex = getBucketIndex(key, capacity); + Entry bucketEntry = buckets[bucketIndex]; + if (bucketEntry == null) { + return null; + } else { + Entry prevEntry = null; + while (bucketEntry != null) { + prevEntry = bucketEntry; + bucketEntry = prevEntry.next; + if (bucketEntry == null) { + return null; + } + if (bucketEntry.getKey() == key) { + return bucketEntry.getValue(); + } else { + return null; + } + + } + } + return null; + } + + public V remove(long key) { + keysCache = null; + valuesCache = null; + int bucketIndex = getBucketIndex(key, capacity); + Entry bucketEntry = buckets[bucketIndex]; + if (bucketEntry == null) { + return null; + } else if (bucketEntry.getKey() == key) { + V value = bucketEntry.getValue(); + buckets[bucketIndex] = null; + size--; + return value; + } else { + Entry prevEntry = null; + while (bucketEntry != null) { + prevEntry = bucketEntry; + bucketEntry = prevEntry.next; + if (bucketEntry.getKey() == key) { + Entry nextEntry = bucketEntry.next; + prevEntry.next = nextEntry; + size--; + return bucketEntry.getValue(); + } + } + } + return null; + } + + public boolean isEmpty() { + return size == 0; + } + + public boolean containsKey(long key) { + int bucketIndex = getBucketIndex(key, capacity); + Entry bucketEntry = buckets[bucketIndex]; + if (bucketEntry != null) { + while (bucketEntry != null) { + if (bucketEntry.getKey() == key) { + return true; + } + bucketEntry = bucketEntry.next; + } + } + return false; + } + + public boolean containsValue(V value) { + V[] values = values(); + return Arrays.asList(values).contains(value); + } + + public long[] keys() { + if (keysCache != null) { + return keysCache; + } + Set keySet = new HashSet(); + for (Entry entry : buckets) { + if (entry != null) { + while (entry != null) { + keySet.add(entry.getKey()); + entry = entry.next; + + } + } + } + + long[] keys = new long[keySet.size()]; + int i = 0; + for (long key : keySet) { + keys[i++] = key; + } + keysCache = keys; + return keys; + } + + @SuppressWarnings("unchecked") + public V[] values() { + if (valuesCache != null) { + return valuesCache; + } + int i = 0; + V[] values = (V[]) Array.newInstance(type, size); + for (Entry entry : buckets) { + if (entry != null) { + while (entry != null) { + values[i] = entry.getValue(); + i++; + entry = entry.next; + + } + } + } + valuesCache = values; + return values; + } + + public long size() { + return size; + } + + public void clear() { + size = 0; + buckets = new Entry[capacity]; + } + + private static class Entry { + private long key; + private V value; - public V remove(long key) { - return null; - } + private Entry next; - public boolean isEmpty() { - return false; - } + public Entry(long key, V value) { + this.key = key; + this.value = value; + } - public boolean containsKey(long key) { - return false; - } + public long getKey() { + return key; + } - public boolean containsValue(V value) { - return false; - } + public V getValue() { + return value; + } - public long[] keys() { - return null; - } + public void setValue(V value) { + this.value = value; + } + } - public V[] values() { - return null; - } + private void resize() { + int newCapacity = capacity + capacity / 2; + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; + } + Entry[] newBuckets = new Entry[newCapacity]; - public long size() { - return 0; - } + 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; + } + } - public void clear() { + capacity = newCapacity; + buckets = newBuckets; + } - } + private int getBucketIndex(long key, int capacity) { + return ((int) Math.abs(key) % capacity); + } } 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..8edaf8a --- /dev/null +++ b/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java @@ -0,0 +1,149 @@ +package de.comparus.opensource.longmap; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class LongMapImplTest { + + private LongMapImpl longMapImpl = new LongMapImpl(); + + @BeforeEach + void fillMap() { + int i = -500_000; + while (i < 500_000) { + longMapImpl.put(i, i); + i++; + } + } + + @Test + void testPut() { + int j = 0; + int i = -500_000; + boolean isAllPutted = false; + while (j < 1_000_000) { + if (longMapImpl.get(i) != null) { + isAllPutted = true; + break; + } + j++; + i++; + } + assertTrue(isAllPutted); + } + + @Test + void testGet() { + int j = 0; + int i = -500_000; + while (j < 1_000_000) { + longMapImpl.get(i); + j++; + i++; + } + assertEquals(longMapImpl.size(), j); + } + + @Test + void testRemove() { + int j = 0; + int i = -500_000; + boolean isAllremoved = true; + while (j < 1_000_000) { + longMapImpl.remove(i); + if (longMapImpl.get(i) != null) { + isAllremoved = false; + break; + } + j++; + i++; + } + assertTrue(isAllremoved); + } + + @Test + void testIsEmpty() { + + assertFalse(longMapImpl.isEmpty()); + longMapImpl.clear(); + assertTrue(longMapImpl.isEmpty()); + } + + @Test + void testContainsKey() { + int i = -500_000; + boolean isContains = true; + while (i < 500_000) { + isContains = longMapImpl.containsKey(i); + if (!isContains) { + break; + } + i++; + } + assertTrue(isContains); + } + + @Test + void testContainsValue() { + int i = -5000; + boolean isContains = true; + while (i < 5000) { + isContains = longMapImpl.containsValue(i); + if (!isContains) { + break; + } + i++; + } + assertTrue(isContains); + } + + @Test + void testKeys() { + long[] expectedKeys = new long[1000000]; + int i = -500_000; + int j = 0; + while (j < expectedKeys.length) { + expectedKeys[j] = i; + i++; + j++; + } + long[] actualKeys = longMapImpl.keys(); + assertEquals(expectedKeys.length, actualKeys.length); + } + + @Test + void testValues() { + Integer[] expectedValues = new Integer[1000000]; + int i = -500_000; + int j = 0; + while (j < expectedValues.length) { + expectedValues[j] = i; + i++; + j++; + } + + assertEquals(expectedValues.length, longMapImpl.values().length); + } + + @Test + void testSize() { + int i = -500_000; + int j = 0; + while (j < 10) { + longMapImpl.remove(i); + i++; + j++; + } + assertTrue(longMapImpl.size() == longMapImpl.values().length); + } + + @Test + void testClear() { + longMapImpl.clear(); + assertTrue(longMapImpl.isEmpty()); + assertTrue(longMapImpl.size() == 0); + } + +} From ef9dce8333849b56fd2dcff65fb55f69c17e0ec4 Mon Sep 17 00:00:00 2001 From: Severyn Date: Mon, 10 Jul 2023 18:27:52 +0300 Subject: [PATCH 2/2] Improved logic of get() method and tests --- .../comparus/opensource/longmap/LongMapImpl.java | 9 +++------ .../opensource/longmap/LongMapImplTest.java | 16 ++++++---------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java index 309656c..a1d318a 100644 --- a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java +++ b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java @@ -86,22 +86,19 @@ public V get(long key) { Entry bucketEntry = buckets[bucketIndex]; if (bucketEntry == null) { return null; + } else if (bucketEntry.getKey() == key) { + return bucketEntry.getValue(); } else { Entry prevEntry = null; while (bucketEntry != null) { prevEntry = bucketEntry; bucketEntry = prevEntry.next; - if (bucketEntry == null) { - return null; - } if (bucketEntry.getKey() == key) { return bucketEntry.getValue(); - } else { - return null; } - } } + return null; } diff --git a/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java b/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java index 8edaf8a..059c723 100644 --- a/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java +++ b/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java @@ -20,15 +20,13 @@ void fillMap() { @Test void testPut() { - int j = 0; int i = -500_000; - boolean isAllPutted = false; - while (j < 1_000_000) { - if (longMapImpl.get(i) != null) { - isAllPutted = true; + boolean isAllPutted = true; + while (i < 500_000) { + if (longMapImpl.get(i) == null) { + isAllPutted = false; break; } - j++; i++; } assertTrue(isAllPutted); @@ -48,16 +46,14 @@ void testGet() { @Test void testRemove() { - int j = 0; int i = -500_000; boolean isAllremoved = true; - while (j < 1_000_000) { + while (i < 1_000_000) { longMapImpl.remove(i); if (longMapImpl.get(i) != null) { isAllremoved = false; break; } - j++; i++; } assertTrue(isAllremoved); @@ -101,7 +97,7 @@ void testContainsValue() { @Test void testKeys() { - long[] expectedKeys = new long[1000000]; + long[] expectedKeys = new long[1_000_000]; int i = -500_000; int j = 0; while (j < expectedKeys.length) {