From 513e4d70bef0ed510b98333868eadcc22c975e2b Mon Sep 17 00:00:00 2001 From: alex-romanchuk <138723953+alex-romanchuk@users.noreply.github.com> Date: Wed, 5 Jul 2023 23:40:31 +0300 Subject: [PATCH 1/3] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1567aa --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# comparustest +comparus test project From f350e5b6831dcc6c8af9ccf01265c222640ec0b8 Mon Sep 17 00:00:00 2001 From: alex-rom Date: Thu, 6 Jul 2023 14:21:18 +0300 Subject: [PATCH 2/3] first version without testing --- README.md | 2 +- .../opensource/longmap/LongMapImpl.java | 231 +++++++++++++++++- 2 files changed, 226 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1c9e2f4..7bb1716 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,4 @@ 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 +* the main aim is to see your codestyle and teststyle diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java index 2f0b54b..60900e0 100644 --- a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java +++ b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java @@ -1,43 +1,262 @@ package de.comparus.opensource.longmap; +import java.util.*; + public class LongMapImpl implements LongMap { + + /** + * Initial size of entries array + */ + private static final int INITIAL_CAPACITY = 16; + /** + * Limit of elements in entries linked list, exceeding it leads to call resize() + */ + private static final int MAX_ENTRY_LENGTH = 16; + private static final float LOAD_FACTOR = 0.75f; + + private Entry[] entries; + + /** + * Total amount of elements in the map + */ + private int count; + + public LongMapImpl() { + this(INITIAL_CAPACITY); + } + + private LongMapImpl(long capacity) { + clearAndResize((int) capacity); + } + + @Override public V put(long key, V value) { - return null; + int index = getIndex(key); + + if (isEmpty()) { + count++; + return addFirstEntry(index, key, value); + + } else { + return putEntry(index, key, value); + } } + @Override public V get(long key) { + if (isEmpty()) { + return null; + } + + for (Entry entry = entries[getIndex(key)]; entry != null; entry = entry.next) { + if (entry.getKey() == key) { + return entry.getValue(); + } + } + return null; } + @Override public V remove(long key) { + if (isEmpty()) { + return null; + } + + int index = getIndex(key); + Entry entry = entries[index]; + + if (entry.getKey() == key) { + //first element replaced by second + entries[index] = entry.next; + return entry.getValue(); + } + + Entry prev = entry; + for (entry = entry.next; entry != null; entry = entry.next) { + + if (entry.getKey() == key) { + //removing current entry by deleting from our entry chain + prev.next = entry.next; + count--; + return entry.getValue(); + } + } + return null; } + @Override public boolean isEmpty() { - return false; + return count == 0; } + @Override public boolean containsKey(long key) { + if (isEmpty()) { + return false; + } + + for (Entry entry = entries[getIndex(key)]; entry != null; entry = entry.next) { + if (entry.getKey() == key) { + return true; + } + } + return false; } + @Override public boolean containsValue(V value) { - return false; + if (isEmpty()) { + return false; + } + + return Arrays.asList(values()).contains(value); } + @Override public long[] keys() { - return null; + if (isEmpty()) { + return new long[]{}; + } + + return Arrays.stream(entries) + .flatMap(entry -> entryKeys(entry).stream()) + .mapToLong(k -> k) + .toArray(); } + @Override public V[] values() { - return null; + if (isEmpty()) { + return (V[]) Collections.emptyList().toArray(); + } + + List values = new ArrayList<>(); + + Arrays.stream(entries) + .flatMap(entry -> entryValues(entry).stream()) + .forEach(values::add); + + return (V[]) values.toArray(); } + @Override public long size() { - return 0; + return count; } + @Override public void clear() { + clearAndResize(INITIAL_CAPACITY); + count = 0; + } + private List entryValues(Entry entry) { + List values = new ArrayList<>(); + + for (; entry != null; entry = entry.next) { + values.add(entry.getValue()); + } + + return values; + } + + private List entryKeys(Entry entry) { + List keys = new ArrayList<>(); + + for (; entry != null; entry = entry.next) { + keys.add(entry.getKey()); + } + + return keys; + } + + private int getIndex(long key) { + return (int) key % entries.length; } + + private V putEntry(int index, long key, V value) { + Entry entry = entries[index]; + + if (entry == null) { + return addFirstEntry(index, key, value); + } + + for (; ; entry = entry.next) { + if (entry.getKey() == key) { + //replace old value with new + V old = entry.getValue(); + entry.setValue(value); + return old; + + } else if (entry.next == null) { + //put new value + entry.next = new Entry<>(key, value); + count++; + resizeIfNeeded(); + return value; + } + } + } + + private V addFirstEntry(int index, long key, V value) { + entries[index] = new Entry<>(key, value); + return value; + } + + private void clearAndResize(int capacity) { + entries = new Entry[capacity]; + } + + private void resizeIfNeeded() { + if (entries.length * MAX_ENTRY_LENGTH * LOAD_FACTOR > count) { + resize(entries.length * 2); + } + } + + private void resize(int capacity) { + clearAndResize(capacity); + + //todo improve + for (long key : this.keys()) { + put(key, this.get(key)); + } + } + + private static class Entry implements Map.Entry { + + final K key; + V value; + Entry next; + + protected Entry(K key, V value) { + this(key, value, null); + } + + protected Entry(K key, V value, Entry next) { + this.key = key; + this.value = value; + this.next = next; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + V old = this.value; + this.value = value; + return old; + } + } + } From a145d3089afa9f32131eb457b0f8607a8f9877bd Mon Sep 17 00:00:00 2001 From: alex-rom Date: Thu, 6 Jul 2023 19:41:13 +0300 Subject: [PATCH 3/3] add tests and fixes --- .../opensource/longmap/LongMapImpl.java | 67 +++++-- .../opensource/longmap/LongMapImplTest.java | 172 ++++++++++++++++++ 2 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java diff --git a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java index 60900e0..4a3e029 100644 --- a/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java +++ b/src/main/java/de/comparus/opensource/longmap/LongMapImpl.java @@ -1,5 +1,6 @@ package de.comparus.opensource.longmap; +import java.lang.reflect.Array; import java.util.*; public class LongMapImpl implements LongMap { @@ -69,6 +70,7 @@ public V remove(long key) { if (entry.getKey() == key) { //first element replaced by second entries[index] = entry.next; + count--; return entry.getValue(); } @@ -130,16 +132,27 @@ public long[] keys() { @Override public V[] values() { if (isEmpty()) { - return (V[]) Collections.emptyList().toArray(); + return null; } - List values = new ArrayList<>(); + Class classType = Arrays.stream(entries) + .filter(Objects::nonNull) + .findAny() + .get() + .getValue() + .getClass(); + V[] arr = (V[]) Array.newInstance(classType, count); + List values = new ArrayList<>(); Arrays.stream(entries) .flatMap(entry -> entryValues(entry).stream()) .forEach(values::add); - return (V[]) values.toArray(); + for (int i = 0; i < values.size(); i++) { + arr[i] = values.get(i); + } + + return arr; } @Override @@ -153,7 +166,7 @@ public void clear() { count = 0; } - private List entryValues(Entry entry) { + private List entryValues(Entry entry) { List values = new ArrayList<>(); for (; entry != null; entry = entry.next) { @@ -163,7 +176,7 @@ private List entryValues(Entry entry) { return values; } - private List entryKeys(Entry entry) { + private List entryKeys(Entry entry) { List keys = new ArrayList<>(); for (; entry != null; entry = entry.next) { @@ -181,6 +194,7 @@ private V putEntry(int index, long key, V value) { Entry entry = entries[index]; if (entry == null) { + count++; return addFirstEntry(index, key, value); } @@ -211,31 +225,60 @@ private void clearAndResize(int capacity) { } private void resizeIfNeeded() { - if (entries.length * MAX_ENTRY_LENGTH * LOAD_FACTOR > count) { + float maxElements = entries.length * MAX_ENTRY_LENGTH * LOAD_FACTOR; + if (maxElements > 0 && maxElements < count) { resize(entries.length * 2); } } private void resize(int capacity) { + if (capacity < 0) { + throw new IllegalArgumentException("Max size has been reached"); + } + + Entry[] entriesOld = entries; clearAndResize(capacity); - //todo improve - for (long key : this.keys()) { - put(key, this.get(key)); + for (Entry entry : entriesOld) { + + for (; entry != null; entry = entry.next) { + Long key = entry.getKey(); + V value = entry.getValue(); + + int newIndex = getIndex(key); + Entry lastEntry = getLastEntry(newIndex); + + if (lastEntry == null) { + addFirstEntry(newIndex, key, value); + } else { + lastEntry.next = new Entry<>(key, value); + } + } + } + } + + private Entry getLastEntry(int index) { + Entry entry = entries[index]; + Entry entryPrevious = null; + + while (entry != null) { + entryPrevious = entry; + entry = entry.next; } + return entryPrevious; } - private static class Entry implements Map.Entry { + private static class Entry implements Map.Entry { final K key; V value; - Entry next; + Entry next; protected Entry(K key, V value) { this(key, value, null); } - protected Entry(K key, V value, Entry next) { + protected Entry(K key, V value, Entry next) { this.key = key; this.value = value; this.next = next; 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..2080755 --- /dev/null +++ b/src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java @@ -0,0 +1,172 @@ +package de.comparus.opensource.longmap; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.stream.IntStream; + +import static org.junit.Assert.*; + +public class LongMapImplTest { + + private LongMapImpl longMap; + + @Test + public void putAndGetTest() { + longMap = new LongMapImpl<>(); + assertEquals(0, longMap.size()); + assertEquals(null, longMap.get(0)); + + String value = longMap.put(0, null); + assertEquals(1, longMap.size()); + assertEquals(null, value); + assertEquals(value, longMap.get(0)); + + value = putIntoMap(1); + assertEquals(2, longMap.size()); + assertEquals(value, getValue(1)); + assertEquals(value, longMap.get(1)); + + value = putIntoMap(2); + assertEquals(3, longMap.size()); + assertEquals(value, getValue(2)); + assertEquals(value, longMap.get(2)); + + value = longMap.put(1, getValue(3)); + assertEquals(3, longMap.size()); + assertEquals(longMap.get(1), getValue(3)); + assertEquals(value, getValue(1)); + + value = putIntoMap(1+16);//to put in the same entry set + assertEquals(4, longMap.size()); + assertEquals(value, getValue(17)); + assertEquals(value, longMap.get(17)); + assertEquals(getValue(3), longMap.get(1)); + + generateValues(1000);//to reach resize case + assertEquals(1000, longMap.size()); + assertEquals(longMap.get(999), getValue(999)); + } + + @Test + public void removeTest() { + longMap = new LongMapImpl<>(); + putIntoMap(1); + putIntoMap(2); + assertEquals(2, longMap.size()); + + String value = longMap.remove(1); + + assertEquals(1, longMap.size()); + assertEquals(getValue(1), value); + assertEquals(getValue(2), longMap.get(2)); + assertNull(longMap.get(1)); + + putIntoMap(2+16);//to put in the same entry set + value = longMap.remove(18); + assertEquals(1, longMap.size()); + assertEquals(getValue(18), value); + assertNull(longMap.get(18)); + + value = longMap.remove(2); + assertEquals(0, longMap.size()); + assertEquals(getValue(2), value); + } + + @Test + public void isEmptyTest() { + longMap = new LongMapImpl<>(); + assertTrue(longMap.isEmpty()); + + putIntoMap(1); + assertFalse(longMap.isEmpty()); + + putIntoMap(33); + assertFalse(longMap.isEmpty()); + + longMap.remove(33); + assertFalse(longMap.isEmpty()); + + longMap.remove(1); + assertTrue(longMap.isEmpty()); + } + + @Test + public void containsKeyValueTest() { + longMap = new LongMapImpl<>(); + assertFalse(longMap.containsKey(1)); + assertFalse(longMap.containsValue(getValue(1))); + + putIntoMap(1); + assertTrue(longMap.containsKey(1)); + assertFalse(longMap.containsKey(33)); + assertTrue(longMap.containsValue(getValue(1))); + assertFalse(longMap.containsValue(getValue(33))); + + putIntoMap(2); + assertTrue(longMap.containsKey(1)); + assertTrue(longMap.containsKey(2)); + assertTrue(longMap.containsValue(getValue(1))); + assertTrue(longMap.containsValue(getValue(2))); + + putIntoMap(33); + assertTrue(longMap.containsKey(1)); + assertTrue(longMap.containsKey(33)); + assertTrue(longMap.containsValue(getValue(1))); + assertTrue(longMap.containsValue(getValue(33))); + + longMap.remove(33); + assertTrue(longMap.containsKey(1)); + assertFalse(longMap.containsKey(33)); + assertTrue(longMap.containsValue(getValue(1))); + assertFalse(longMap.containsValue(getValue(33))); + + longMap.remove(1); + assertFalse(longMap.containsKey(1)); + assertFalse(longMap.containsKey(33)); + assertFalse(longMap.containsValue(getValue(1))); + assertFalse(longMap.containsValue(getValue(33))); + } + + @Test + public void keysValuesTest() { + longMap = new LongMapImpl<>(); + assertEquals(0, longMap.keys().length); + assertNull(longMap.values()); + + generateValues(1000); + long[] keys = longMap.keys(); + assertEquals(1000, keys.length); + assertTrue(Arrays.stream(keys).anyMatch(key -> key == 333)); + String[] values = longMap.values(); + assertEquals(1000, longMap.values().length); + assertTrue(Arrays.stream(values).anyMatch(v -> getValue(333).equals(v))); + + longMap.clear(); + assertEquals(0, longMap.keys().length); + assertNull(longMap.values()); + } + + @Test + public void clearTest() { + longMap = new LongMapImpl<>(); + generateValues(1000); + assertEquals(1000, longMap.size()); + + longMap.clear(); + assertEquals(0, longMap.size()); + assertTrue(longMap.isEmpty()); + } + + private void generateValues(int amount) { + IntStream.range(0, amount) + .forEach(this::putIntoMap); + } + + private String putIntoMap(int index) { + return (String) longMap.put(index, getValue(index)); + } + private String getValue(int index) { + return "obj" + index; + } +} \ No newline at end of file