diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..8ea8ffe
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,36 @@
+# Use the latest 2.1 version of CircleCI pipeline process engine.
+# See: https://circleci.com/docs/2.0/configuration-reference
+version: 2.1
+
+# Define a job to be invoked later in a workflow.
+# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
+jobs:
+ # Below is the definition of your job to build and test your app, you can rename and customize it as you want.
+ build-and-test:
+ # These next lines define a Docker executor: https://circleci.com/docs/2.0/executor-types/
+ # You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
+ # Be sure to update the Docker image tag below to openjdk version of your application.
+ # A list of available CircleCI Docker Convenience Images are available here: https://circleci.com/developer/images/image/cimg/openjdk
+ docker:
+ - image: cimg/openjdk:11.0
+ # Add steps to the job
+ # See: https://circleci.com/docs/2.0/configuration-reference/#steps
+ steps:
+ # Checkout the code as the first step.
+ - checkout
+ # Use mvn clean and package as the standard maven build phase
+ - run:
+ name: Build
+ command: mvn -B -DskipTests clean package
+ # Then run your tests!
+ - run:
+ name: Test
+ command: mvn test
+
+# Invoke jobs via workflows
+# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
+workflows:
+ sample: # This is the name of the workflow, feel free to change it to better match your workflow.
+ # Inside the workflow, you define the jobs you want to run.
+ jobs:
+ - build-and-test
diff --git a/.gitignore b/.gitignore
index a870eaf..3a0f4e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.idea/
+.target/
long-map.iml
\ No newline at end of file
diff --git a/README.md b/README.md
index 7bb1716..926ad3f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,11 @@
# long-map
-Finish development of class LongMapImpl, which implements a map with keys of type long. It has to be a hash table (like HashMap). Requirements:
-* it should not use any known Map implementations;
-* it should use as less memory as possible and have adequate performance;
-* the main aim is to see your codestyle and teststyle
+## unit tests: [](https://dl.circleci.com/status-badge/redirect/gh/Artemiy7/long-map/tree/master)
+
+## time complexity text:
+I added 201 entries to the LongMap using different numbers and displayed the distribution of entries in buckets on the graph.
+-  Average case using new Random().nextlong()
+-  Worst case using numbers with same ending number e.g.: 7777, 7777777, 1113337
+-  Log(201) for comparison
+
+
diff --git a/pom.xml b/pom.xml
index 36c092b..ac33188 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,5 +32,10 @@
4.12
test
+
+ junit
+ junit
+ 4.13.2
+
diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
index 2f0b54b..4a1b3d4 100644
--- a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
+++ b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
@@ -1,43 +1,324 @@
package de.comparus.opensource.longmap;
+import java.lang.reflect.Array;
+import java.util.*;
+
public class LongMapImpl implements LongMap {
+
+ private final int DEFAULT_INITIAL_CAPACITY;
+ private final float LOAD_FACTOR;
+
+ private transient LongEntry[] table;
+ /** The total number of entries in the map.*/
+
+ private int count;
+ /** The table is resized when its size exceeds this threshold. The value of this field is (int)(capacity * loadFactor)*/
+ private int threshold;
+
+ public LongMapImpl() {
+ this(16, 0.75f);
+ }
+
+ /**
+ * Constructs a new map with the given LongMap.
+ */
+ public LongMapImpl(LongMap extends V> longMap) {
+ this((int) (2*longMap.size()), 0.75f);
+ putAll(longMap);
+ }
+
+ public LongMapImpl(int initialCapacity, float loadFactor) {
+ if (initialCapacity < 0)
+ throw new IllegalArgumentException("Illegal argument capacity: " + initialCapacity);
+ if (loadFactor < 0 || Float.isNaN(loadFactor))
+ throw new IllegalArgumentException("Illegal load capacity: " + loadFactor);
+
+ if (initialCapacity==0)
+ initialCapacity = 1;
+
+ DEFAULT_INITIAL_CAPACITY = initialCapacity;
+ LOAD_FACTOR = loadFactor;
+ threshold = (int) (DEFAULT_INITIAL_CAPACITY * LOAD_FACTOR);
+ table = new LongEntry[DEFAULT_INITIAL_CAPACITY];
+ count = 0;
+ }
+
+ /**
+ * If number of keys in the table exceeds threshold
+ * method increases the table size to (oldSize * 2)
+ * and recalculates the index of every key in the table.
+ */
+ @Override
public V put(long key, V value) {
- return null;
+ if (count >= threshold) {
+ resize();
+ }
+ LongEntry newEntry = new LongEntry<>(key, value);
+ int index = calculateIndex(key);
+ return putEntry(newEntry, index);
+ }
+
+ private V putEntry(LongEntry newEntry, int index) {
+ if (table[index] == null) {
+ table[index] = newEntry;
+ count++;
+ } else {
+ for (LongEntry entry = table[index]; entry != null;) {
+ if (entry.getKey() == newEntry.getKey()) {
+ entry.value = newEntry.value;
+ return newEntry.value;
+ } else if (entry.next == null) {
+ entry.next = newEntry;
+ count++;
+ break;
+ }
+ entry = entry.next;
+ }
+ }
+ return newEntry.value;
+ }
+
+ /**
+ * Copies all the entries from the longMap to this table.
+ */
+ public void putAll(LongMap extends V> longMap) {
+ if (longMap != null) {
+ long[] keys = longMap.keys();
+ for (int i = keys.length; i-- > 0; ) {
+ put(keys[i], longMap.get(keys[i]));
+ }
+ }
}
+
+ private int calculateIndex(long key) {
+ if (key == 0)
+ return 0;
+ else
+ return (Long.hashCode(key) & 0x7FFFFFFF) % table.length;
+ }
+
+ /**
+ * Resizes table and increases the capacity of table to (oldSize * 2).
+ * Recalculates the index of every key in order to make map operations more efficient.
+ * This method is called automatically when the
+ * number of keys in the table exceeds threshold.
+ */
+ private void resize() {
+ int oldSize = this.table.length;
+ int newSize = oldSize * 2;
+ LongEntry[] newTable = new LongEntry[newSize];
+ LongEntry[] oldTable = this.table;
+
+ this.threshold = (int)(newSize * LOAD_FACTOR);
+ this.table = newTable;
+
+ for (int i = oldSize; i-- > 0;) {
+ for (LongEntry entry = oldTable[i]; entry != null;) {
+ int index = calculateIndex(entry.getKey());
+ LongEntry next = entry.next;
+ if (table[index] == null) {
+ table[index] = entry;
+ table[index].next = null;
+ } else {
+ for (LongEntry newTableEntry = table[index]; newTableEntry != null;) {
+ if (newTableEntry.next == null) {
+ newTableEntry.next = entry;
+ newTableEntry.next.next = null;
+ }
+ newTableEntry = newTableEntry.next;
+ }
+ }
+ entry = next;
+ }
+ }
+ }
+
+ /**
+ * Searches for value by key in a table.
+ * @return a value if the key is present in the table
+ * @return null if the key is not found in the table
+ */
+ @Override
public V get(long key) {
+ int index = calculateIndex(key);
+ for (LongEntry entry = table[index]; entry != null;) {
+ if (key == entry.key) {
+ return entry.value;
+ }
+ entry = entry.next;
+ }
return null;
}
+ @Override
public V remove(long key) {
+ int index = calculateIndex(key);
+ if (table[index] == null)
+ return null;
+
+ LongEntry previous = null;
+ for (LongEntry entry = table[index]; entry != null;) {
+ if (key == entry.key) {
+ if (previous == null)
+ table[index] = table[index].next;
+ else
+ previous.next = entry.next;
+ count--;
+ return entry.value;
+ }
+ previous = entry;
+ entry = entry.next;
+ }
return null;
}
+ @Override
public boolean isEmpty() {
- return false;
+ return count == 0;
}
+ @Override
public boolean containsKey(long key) {
- return false;
+ return get(key) != null;
}
+ @Override
public boolean containsValue(V value) {
+ for (int i = count; i-- > 0 ;) {
+ for (LongEntry entry = table[i]; entry != null ; entry = entry.next) {
+ if (entry.value.equals(value)) {
+ return true;
+ }
+ }
+ }
return false;
}
+ @Override
public long[] keys() {
- return null;
+ long[] keys = new long[count];
+ int keysIndex = 0;
+
+ for (int i = table.length; i-- > 0;) {
+ for (LongEntry entry = table[i]; entry != null;) {
+ keys[keysIndex] = entry.key;
+ keysIndex++;
+ entry = entry.next;
+ }
+ }
+ return keys;
}
- public V[] values() {
- return null;
+
+ /**
+ * @throws ArrayIndexOutOfBoundsException if map is empty
+ */
+ @Override
+ public V[] values() throws ArrayIndexOutOfBoundsException {
+ V[] values = (V[]) new Object[count];
+ int valueIndex = 0;
+ for (int i = table.length; i-- > 0;) {
+ for (LongEntry entry = table[i]; entry != null;) {
+ values[valueIndex] = entry.value;
+ valueIndex++;
+ entry = entry.next;
+ }
+ }
+ return (V[]) Array.newInstance(values[0].getClass(), values.length);
}
+ @Override
public long size() {
- return 0;
+ return count;
}
+ @Override
public void clear() {
+ for (int i = table.length; i-- > 0;) {
+ table[i] = null;
+ }
+ count = 0;
+ System.gc();
+ }
+
+ public Set> entrySet() {
+ Set> entrySet = new HashSet<>();
+ for (int i = table.length; i-- > 0;) {
+ for (LongEntry entry = table[i]; entry != null;) {
+ entrySet.add(entry);
+ entry = entry.next;
+ }
+ }
+ return entrySet;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(keys());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ LongMapImpl> longMap = (LongMapImpl>) o;
+ return Arrays.equals(keys(), longMap.keys());
+ }
+
+ @Override
+ public String toString() {
+ int capacity = count;
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("{");
+ for (int i = table.length; i-- > 0;) {
+ for (LongEntry entry = table[i]; entry != null;) {
+ stringBuilder.append(entry.toString());
+ stringBuilder.append(", ");
+ entry = entry.next;
+ capacity--;
+ }
+ if (capacity == 0 && stringBuilder.length() > 1)
+ return stringBuilder.replace(stringBuilder.length()-2, stringBuilder.length(), "")
+ .append("}").toString();
+ }
+ return "{}";
+ }
+
+ private static class LongEntry {
+ final long key;
+ V value;
+ LongEntry next;
+
+ LongEntry(long key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public long getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ LongEntry> longEntry = (LongEntry>) o;
+ return key == longEntry.key;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key);
+ }
+ @Override
+ public String toString() {
+ return this.key + "=" + this.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..ef9ed3a
--- /dev/null
+++ b/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java
@@ -0,0 +1,283 @@
+package de.comparus.opensource.longmap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class LongMapImplTest {
+ LongMapImpl longMap;
+ @Before
+ public void before() {
+ longMap = new LongMapImpl<>();
+ }
+
+ @Test
+ public void putEntry_Test() {
+ longMap.put(1000000000l, "Str");
+ longMap.put(1000000001l, "Str1");
+ longMap.put(1000000002l, "Str2");
+
+ assert longMap.get(1000000000l).equals("Str");
+ assert longMap.get(1000000001l).equals("Str1");
+ assert longMap.get(1000000002l).equals("Str2");
+ assert longMap.size() == 3;
+ }
+
+ @Test
+ public void putDuplicateValue_Test() {
+ longMap.put(1000000000l, "Str");
+ longMap.put(1000000000l, "Str1");
+ longMap.put(1000000000l, "Str2");
+ longMap.put(1000000000l, "Str3");
+
+ assert longMap.get(1000000000l).equals("Str3");
+ assert longMap.size() == 1;
+ }
+
+ @Test
+ public void putDuplicateNullValue_Test() {
+ longMap.put(0, "Str");
+ longMap.put(0, "Str1");
+ longMap.put(0, "Str2");
+
+ assert longMap.get(0).equals("Str2");
+ assert longMap.size() == 1;
+ }
+
+
+ @Test
+ public void put101Entries_Test() {
+ for (int i = 0; i < 101; i++) {
+ longMap.put((i * 3331111), "i = " + i);
+ }
+ assert longMap.size() == 101;
+ for (int i = 0; i < 101; i++) {
+ assert longMap.get(i * 3331111).equals("i = " + i);
+ }
+ }
+
+ @Test
+ public void removeEntry_Test() {
+ longMap.put(1000000000l, "Str");
+ longMap.put(1000000001l, "Str1");
+ longMap.put(1000000002l, "Str2");
+
+ assert longMap.remove(1000000000l).equals("Str");
+ assert longMap.remove(1000000001l).equals("Str1");
+ assert longMap.remove(1000000002l).equals("Str2");
+ assert longMap.size() == 0;
+ }
+
+ @Test
+ public void removeEntry_NotOkTest() {
+ longMap.put(1000000000l, "Str");
+
+ assert longMap.remove(1000000001l) == null;
+ assert longMap.size() == 1;
+ }
+
+ @Test
+ public void removeEntryFromEmptyMap_Test() {
+ assert longMap.remove(1000000001l) == null;
+ assert longMap.size() == 0;
+ }
+
+ @Test
+ public void remove101Entries_Test() {
+ for (int i = 0; i < 101; i++) {
+ longMap.put((i * 3331111), "i = " + i);
+ }
+ assert longMap.size() == 101;
+ for (int i = 0; i < 101; i++) {
+ longMap.remove(i * 3331111).equals("i = " + i);
+ }
+ assert longMap.size() == 0;
+ }
+
+ @Test
+ public void keys_Test() {
+ longMap = new LongMapImpl<>();
+ longMap.put(1000000000l, "Str");
+
+ assert longMap.keys()[0] == 1000000000l;
+ assert longMap.size() == 1;
+ }
+
+ @Test
+ public void keys101_Test() {
+ for (int i = 0; i < 101; i++) {
+ longMap.put((i * 3331111), "i = " + i);
+ }
+ long[] keys = longMap.keys();
+ assert keys.length == 101;
+ for (int i = 0; i < keys.length; i++) {
+ longMap.remove(keys[i]);
+ }
+ assert longMap.size() == 0;
+ }
+
+ @Test
+ public void values_Test() {
+ longMap.put(1000000000l, "Str");
+
+ assert longMap.keys()[0] == 1000000000l;
+ assert longMap.size() == 1;
+ }
+
+ @Test
+ public void values101_Test() {
+ for (int i = 0; i < 101; i++) {
+ longMap.put((i * 3331111), "i = " + i);
+ }
+ String[] values = longMap.values();
+ assert values.length == 101;
+ }
+
+ @Test
+ public void containsKeyTest() {
+ longMap.put(1000000000l, "Str");
+
+ assert longMap.containsKey(1000000000l);
+ assert !longMap.containsKey(1000000001l);
+ }
+
+ @Test
+ public void containsKey_NotOkTest() {
+ longMap.put(1000000000l, "Str");
+
+ assert !longMap.containsKey(1000000001l);
+ assert longMap.size() == 1;
+ }
+
+ @Test
+ public void containsKey101_Test() {
+ for (int i = 0; i < 101; i++) {
+ longMap.put((i * 3331111), "i = " + i);
+ }
+ for (int i = 0; i < 101; i++) {
+ assert longMap.containsKey(i * 3331111);
+ }
+ }
+
+ @Test
+ public void containsValue_Test() {
+ longMap.put(1000000000l, "Str");
+
+ assert longMap.containsValue("Str");
+ }
+
+ @Test
+ public void containsValue_NotOkTest() {
+ longMap.put(1000000000l, "Str");
+
+ assert !longMap.containsValue("Str1");
+ assert longMap.size() == 1;
+ }
+
+ @Test
+ public void containsValue101_Test() {
+ for (int i = 0; i < 101; i++) {
+ longMap.put((i * 3331111), "i = " + i);
+ }
+ for (int i = 0; i < 101; i++) {
+ assert longMap.containsKey(i * 3331111);
+ }
+ }
+
+ @Test
+ public void putAll_Test() {
+ longMap = new LongMapImpl<>();
+ longMap.put(10000000007l, "Str7");
+ LongMapImpl insertLongMap = new LongMapImpl<>();
+ insertLongMap.put(10000000008l, "Str8");
+ insertLongMap.put(10000000009l, "Str9");
+ insertLongMap.put(100000000010l, "Str10");
+
+ assert longMap.size() == 1;
+ longMap.putAll(insertLongMap);
+ assert longMap.size() == 4;
+ assert longMap.get(10000000007l).equals("Str7");
+ assert longMap.get(10000000008l).equals("Str8");
+ assert longMap.get(10000000009l).equals("Str9");
+ assert longMap.get(100000000010l).equals("Str10");
+ }
+
+ @Test
+ public void putAllDuplicate_Test() {
+ longMap = new LongMapImpl<>();
+ longMap.put(10000000007l, "Str7");
+ LongMapImpl insertLongMap = new LongMapImpl<>();
+ insertLongMap.put(10000000007l, "Str77");
+ insertLongMap.put(10000000008l, "Str8");
+ insertLongMap.put(10000000009l, "Str9");
+
+ assert longMap.size() == 1;
+ longMap.putAll(insertLongMap);
+ assert longMap.size() == 3;
+ assert longMap.get(10000000007l).equals("Str77");
+ assert longMap.get(10000000008l).equals("Str8");
+ assert longMap.get(10000000009l).equals("Str9");
+ }
+
+ @Test
+ public void mapValueConstructor_Test() {
+ LongMapImpl insertLongMap = new LongMapImpl<>();
+ insertLongMap.put(10000000007l, "Str7");
+ insertLongMap.put(10000000008l, "Str8");
+ insertLongMap.put(10000000009l, "Str9");
+
+ longMap = new LongMapImpl<>(insertLongMap);
+ longMap.put(100000000010l, "Str10");
+
+ assert longMap.size() == 4;
+ assert longMap.get(10000000007l) == "Str7";
+ assert longMap.get(10000000008l) == "Str8";
+ assert longMap.get(10000000009l) == "Str9";
+ assert longMap.get(100000000010l) == "Str10";
+ }
+
+ @Test
+ public void mapValueConstructorDuplicate_Test() {
+ LongMapImpl insertLongMap = new LongMapImpl<>();
+ insertLongMap.put(10000000007l, "Str7");
+ insertLongMap.put(10000000008l, "Str8");
+ insertLongMap.put(10000000009l, "Str9");
+
+ longMap = new LongMapImpl<>(insertLongMap);
+ longMap.put(10000000009l, "Str99");
+
+ assert longMap.size() == 3;
+ assert longMap.get(10000000007l) == "Str7";
+ assert longMap.get(10000000008l) == "Str8";
+ assert longMap.get(10000000009l) == "Str99";
+ }
+
+ @Test
+ public void toString_Test() {
+ longMap.put(10000000001l, "Str");
+
+ String result = "{10000000001=Str}";
+
+ assert longMap.toString().equals(result);
+ }
+
+ @Test
+ public void toStringEmpty_Test() {
+ assert longMap.toString().equals("{}");
+ }
+
+ @Test
+ public void clear_Test() {
+ longMap.clear();
+ assert longMap.size() == 0;
+ }
+
+ @Test
+ public void size_Test() {
+ assert longMap.size() == 0;
+ }
+
+ @Test
+ public void isEmpty_Test() {
+ assert longMap.isEmpty();
+ }
+}