Custom implementation of ```ArrayMap``` Using ```AbstractMap``` JAVA - java

I am trying to implement an ArrayMap<K,V> based on an AbstractMap<K, V> but I don't know why my entrySet() should return and I have tried to make it return something using AbstractSet but it really doesn't like my Arraylist of entries...
public class ArrayMap<K, V> extends AbstractMap<K, V> {
private Set<Entry<K, V>> entries = null;
private ArrayList<ArrayMapEntry<K, V>> list;
public ArrayMap() {
list = new ArrayList<ArrayMapEntry<K, V>>();
}
public ArrayMap(int initialCapacity) {
list = new ArrayList<ArrayMapEntry<K, V>>(initialCapacity);
}
#Override
public Set<Entry<K, V>> entrySet() {
if (entries == null) {
entries = new AbstractSet<Entry<K, V>>() {
#Override
public void clear() {
list.clear();
}
#Override
public Iterator<Entry<K, V>> iterator() {
return list.iterator();
}
#Override
public int size() {
return list.size();
}
};
}
return entries;
}
static class ArrayMapEntry<K, V> implements Map.Entry<K, V> {
protected K key;
protected V value;
public ArrayMapEntry(K key, V value) {
this.key = key;
this.value = value;
}
#Override
public K getKey() {
return key;
}
#Override
public V getValue() {
return value;
}
#Override
public V setValue(V v) {
value = v;
return v;
}
public String toString() { return key + " : " + value; }
public boolean equals(Object o) {
if (!(o instanceof ArrayMapEntry)) {
return false;
}
ArrayMapEntry e = (ArrayMapEntry) o;
return (key == null? e.getKey() == null : key.equals(e.getKey()))
&& (value == null ? e.getValue() == null : value.equals(e.getValue()));
}
public int hashCode() {
int keyHash = (key == null ? 0 : key.hashCode());
int valueHash = (value == null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}
}

Related

How to return List<E> from Collection<V> where E is contained inside V?

I have a map which returns below data when Map.values() is called which returns Collection<V>
[
Cache.CachedObject(inserted=1617483447407, value=Record(id=10, type=5, timestamp=2021-04-03T08:37:51.312Z)),
Cache.CachedObject(inserted=1617483446133, value=Record(id=11, type=6, timestamp=2021-04-03T08:37:51.312Z)),
Cache.CachedObject(inserted=1617483445030, value=Record(id=8, type=4, timestamp=2021-04-03T08:37:51.312Z))
]
How do I return List<Record> from Collection<V> ?
Code of Cache class is given below
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.api.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
public class Cache<K, V> {
private long timeToLive = 20000L;
private HashMap<K, V> cacheMap = new HashMap();
public Cache() {
if (this.timeToLive > 0L) {
Thread t = new Thread(() -> {
while(true) {
this.cleanup();
}
});
t.setDaemon(true);
t.start();
}
}
public void put(K key, V value) {
synchronized(this.cacheMap) {
this.cacheMap.put(key, new Cache.CachedObject(value));
}
}
public V get(K key) {
synchronized(this.cacheMap) {
Cache<K, V>.CachedObject c = (Cache.CachedObject)this.cacheMap.get(key);
return c == null ? null : c.value;
}
}
public void remove(K key) {
synchronized(this.cacheMap) {
this.cacheMap.remove(key);
}
}
public int size() {
synchronized(this.cacheMap) {
return this.cacheMap.size();
}
}
public void clear() {
synchronized(this.cacheMap) {
this.cacheMap.clear();
}
}
public Collection<V> values() {
synchronized(this.cacheMap) {
return this.cacheMap.values();
}
}
public void cleanup() {
long now = System.currentTimeMillis();
ArrayList deleteKey;
synchronized(this.cacheMap) {
Iterator<Entry<K, V>> itr = this.cacheMap.entrySet().iterator();
deleteKey = new ArrayList(this.cacheMap.size() / 2 + 1);
while(itr.hasNext()) {
Entry<K, V> entry = (Entry)itr.next();
K key = entry.getKey();
V cached = entry.getValue();
if (cached != null && now > ((Cache.CachedObject)cached).inserted + this.timeToLive) {
deleteKey.add(key);
}
}
}
for(Iterator var4 = deleteKey.iterator(); var4.hasNext(); Thread.yield()) {
K key = var4.next();
synchronized(this.cacheMap) {
this.cacheMap.remove(key);
}
}
}
public String toString() {
return "Cache(timeToLive=" + this.timeToLive + ", cacheMap=" + this.cacheMap + ")";
}
private class CachedObject {
public long inserted = System.currentTimeMillis();
public V value;
protected CachedObject(V value) {
this.value = value;
}
public String toString() {
return "Cache.CachedObject(inserted=" + this.getInserted() + ", value=" + this.getValue() + ")";
}
public long getInserted() {
return this.inserted;
}
public V getValue() {
return this.value;
}
}
}
Update
To resolve multiple compilation and design issues in the posted code of Cache /CachedObject, the following fixes need to be applied (but they are not final and further improvements are possible):
Make inner class CachedObject generic
Replace HashMap with ConcurrentHashMap (so that synchronized blocks could be removed) and fix the type of value in this map to be CachedObject<V>
Refactor cleanUp method
An example implementation
public class Cache<K, V> {
private long timeToLive = 20000L;
private Map<K, CachedObject<V>> cacheMap = new ConcurrentHashMap<>();
public Cache() {
if (this.timeToLive > 0L) {
Thread t = new Thread(() -> {
while(true) {
this.cleanup();
}
});
t.setDaemon(true);
t.start();
}
}
public void put(K key, V value) {
this.cacheMap.put(key, new CachedObject(value));
}
public V get(K key) {
CachedObject<V> c = this.cacheMap.get(key);
return c == null ? null : c.value;
}
public void remove(K key) {
this.cacheMap.remove(key);
}
public int size() {
return this.cacheMap.size();
}
public void clear() {
this.cacheMap.clear();
}
public Collection<V> values() {
return this.cacheMap.values().stream()
.map(CachedObject::getValue).collect(Collectors.toList());
}
public void cleanup() {
if (!this.cacheMap.isEmpty()) {
long now = System.currentTimeMillis();
this.cacheMap.entrySet().removeIf(e -> null == e.getValue() || now > e.getValue().inserted + this.timeToLive);
}
Thread.yield();
}
public String toString() {
return "Cache(timeToLive=" + this.timeToLive + ", cacheMap=" + this.cacheMap + ")";
}
private class CachedObject<V> {
public long inserted = System.currentTimeMillis();
public V value;
protected CachedObject(V value) {
this.value = value;
}
public String toString() {
return "Cache.CachedObject(inserted=" + this.getInserted() + ", value=" + this.getValue() + ")";
}
public long getInserted() {
return this.inserted;
}
public V getValue() {
return this.value;
}
}
}
With this implementation, the method Cache::values() provides an appropriate collection of V-type elements which are copied into a list, so it will be sufficient just to cast to List:
Cache<String, Record> cache = new Cache<>();
cache.put("#1", new Record(1));
cache.put("#2", new Record(2));
cache.put("#3", new Record(3));
System.out.println(cache);
List<Record> records = (List<Record>) cache.values();
System.out.println(records);
System.out.println(records);
Thread.sleep(2_100L);
List<Record> noRecords = (List) cache.values();
System.out.println(noRecords);
Output
Cache(timeToLive=2000, cacheMap={#3=Cache.CachedObject(inserted=1617530470001, value=Record{id=3}), #1=Cache.CachedObject(inserted=1617530470001, value=Record{id=1}), #2=Cache.CachedObject(inserted=1617530470001, value=Record{id=2})})
[Record{id=3}, Record{id=1}, Record{id=2}]
[]
If you don't mind using Eclipse Collections (which is a great library by the way, an extra dependency however...) , you might want to use the following
List<String> valuesList = Lists.mutable
.ofAll(values)
.collect(CachedObject::getValue);
I solved it. Decided to implement library Cache<K, V> class with minor change to values method.
public Collection<V> values() {
synchronized (cacheMap) {
return cacheMap
.values()
.stream()
.map(cache -> ((CachedObject) cache).getValue())
.collect(Collectors.toList());
}
}
Can someone please conform if its the right way

Java Linkedhashmap equals method custom implementation

I am trying to implement my own LinkedHashMap using chaining strategy for deal with collisions. Below code snippet shows what I have tried so far.
CustomLinkedHashMap
public class CustomLinkedHashMap<K, V> {
private Entry<K, V>[] table;
private int capacity = 4;
private Entry<K, V> header;
private Entry<K, V> last;
#SuppressWarnings("unchecked")
public CustomLinkedHashMap() {
table = new Entry[capacity];
}
#SuppressWarnings("unchecked")
public boolean equals(Object that) {
if (!(that instanceof CustomLinkedHashMap)) {
return false;
}
CustomLinkedHashMap<K, V> other = (CustomLinkedHashMap<K, V>) that;
// if lists are empty
if (header == null) {
return other.header == null;
}
if (!header.equals(other.header)) {
return false;
}
// Just one element
if (header == last) {
return true;
}
if (!header.equals(other.last)) {
return false;
}
Entry<K, V> thisNode = header;
Entry<K, V> otherNode = other.header;
while (thisNode.next != last) {
thisNode = thisNode.next;
otherNode = otherNode.next;
if (!(thisNode.equals(otherNode))) {
return false;
}
}
return true;
}
public void put(K newKey, V data) {
if (newKey == null)
return; //nulls not allowed
int hash = hash(newKey);
Entry<K, V> newEntry = new Entry<K, V>(newKey, data, null);
maintainOrderAfterInsert(newEntry);
if (table[hash] == null) {
table[hash] = newEntry;
} else {
Entry<K, V> previous = null;
Entry<K, V> current = table[hash];
while (current != null) { // reached to the last entry of bucket.
if (current.key.equals(newKey)) {
if (previous == null) { //node has to be insert on first of bucket.
newEntry.next = current.next;
table[hash] = newEntry;
return;
} else {
newEntry.next = current.next;
previous.next = newEntry;
return;
}
}
previous = current;
current = current.next;
}
previous.next = newEntry;
}
}
public boolean isEmpty() {
return header == null;
}
public void clear() {
if (table != null && capacity > 0) {
header = null;
last = null;
capacity = 0;
}
}
public void printMap() {
Entry<K, V> currentEntry = header;
while (currentEntry != null) {
System.out.print("{" + currentEntry.key + "=" + currentEntry.value + "}" + " ");
currentEntry = currentEntry.after;
}
}
private int hash(K key) {
return Math.abs(key.hashCode()) % capacity;
}
static class Entry<K, V> {
K key;
V value;
Entry<K, V> next;
Entry<K, V> before, after;
public Entry(K key, V value, Entry<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
#SuppressWarnings("unchecked")
public boolean equals(Object object) {
if (object == this)
return true;
if (object instanceof Entry) {
Entry<K, V> e = (Entry<K, V>) object;
if (Objects.equals(key, e.key) && Objects.equals(value, e.value)) {
return true;
}
}
return false;
}
}
}
CustomLinkedHashMap class Full implementation here.
As for my implementation when I try equals method it always returns me false.
CustomLinkedHashMap<Integer, String> lhm = new CustomLinkedHashMap<>();
lhm.put(13, "John");
lhm.put(3, "Holmes");
lhm.put(19, "Jenifer");
CustomLinkedHashMap<Integer, String> lhm2 = new CustomLinkedHashMap<>();
lhm2.put(13, "John");
lhm2.put(3, "Holmes");
lhm2.put(19, "Jenifer");
System.out.println(lhm.equals(lhm2)); // Returns true when using java.util.LinkedHashMap
Any suggestions would be appreciable.
Your equals for CustomLinkedHashMap is way too complicated. All you really need is to check the entries (and their ordering):
#Override
public boolean equals(#Nullable Object thatObject) {
if (!(thatObject instanceof CustomLinkedHashMap)) {
return false;
}
CustomLinkedHashMap<?,?> thatMap =
(CustomLinkedHashMap<?,?>) thatObject;
return Arrays.equals(this.table, thatMap.table);
}
You also need a solid implementation for Entry, which checks the key and value against another:
#Override
public boolean equals(#Nullable Object thatObject) {
if (!(thatObject instanceof Entry)) {
return false;
}
Entry<?,?> thatEntry = (Entry<?,?>) thatObject;
if (!Objects.equals(this.key, thatEntry.key)) {
return false;
}
return Objects.equals(this.value, thatEntry.value));
}

What is the meaning of error message: The return type is incompatible

I'm trying to create the entrySet() method for a custom HashMap. I've attached both my entry class, called MyEntry, and my entrySet() method.
I receive the error:
The return type is incompatible with Map<K,V>.entrySet()
... and the return type is underlined in red. I don't understand what this is expressing.
#Override
public Set<HashMap<K,V>.MyEntry<K, V>> entrySet(){
if (entrySetView != null) return entrySetView;
else return entrySetView = new AbstractSet<HashMap<K,V>.MyEntry<K,V>>() {
#Override
public Iterator<HashMap<K,V>.MyEntry<K, V>> iterator() {
return new HashIterator<HashMap<K,V>.MyEntry<K, V>>() {
#Override
public HashMap<K,V>.MyEntry<K, V> next() {
return nextEntry();
}
};
}
#Override
public int size() {
return HashMap.this.size();
}
#Override
public void clear() {
HashMap.this.clear();
}
};
}
public class MyEntry<K, V> implements Map.Entry<K,V>{
private K key;
private V value;
private MyEntry<K,V> next;
private int hash;
public MyEntry() {
counter++;
}
public MyEntry(K key, V value, MyEntry<K,V> next, int h) {
this.key = key;
this.value = value;
this.next = next;
this.hash = h;
counter++;
}
public boolean equals(Object o) {
if(this == o) return true;
if (!(o instanceof MyEntry))
return false;
MyEntry entry = (MyEntry)o;
Object k1 = getKey();
Object k2 = entry.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = entry.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setKey(K key) {
this.key = key;
}
public Object setValue(Object val) {
V oldValue = value;
value = (V) val;
return oldValue;
}
public MyEntry<K,V> getNext(){
return next;
}
public void setNext(MyEntry<K,V> n) {
this.next = n;
}
public String toString() {
return "{" + getKey() + "," + getValue() + "}";
}
}
entrySet of the Map interface has a return type of Set<Map.Entry<K, V>>, so you can't return a Set<HashMap<K,V>.MyEntry<K, V>>.
Set<HashMap<K,V>.MyEntry<K, V>> is not a sub-interface of Set<Map.Entry<K, V>>.
I believe something like this should work (you might have to change the type of entrySetView as well):
#Override
public Set<Map.Entry<K, V>> entrySet(){
if (entrySetView != null)
return entrySetView;
else
return entrySetView = new AbstractSet<Map.Entry<K, V>>() {
#Override
public Iterator<Map.Entry<K, V>> iterator() {
return new HashIterator<Map.Entry<K, V>>() {
#Override
public Map.Entry<K, V> next() {
return nextEntry();
}
};
}
#Override
public int size() {
return HashMap.this.size();
}
#Override
public void clear() {
HashMap.this.clear();
}
};
}

invalid hash entry after 100000 + entries

I've implemented a HashMap with Linear Probing for hash collisions.
import java.util.Optional;
#SuppressWarnings("unchecked")
public abstract class HashMap<T, R> {
private static final int MIN_CAPACITY = 2;
private Entry<T, R>[] table;
private int internalSize, size;
private float fillRatio;
public HashMap() {
this(MIN_CAPACITY);
}
public HashMap(int initialCapacity) {
this(initialCapacity, .75f);
}
public HashMap(int initialCapacity, float fillRatio) {
this.table = new Entry[Math.max(MIN_CAPACITY, initialCapacity)];
this.fillRatio = fillRatio;
}
public Optional<R> put(T key, R value) {
int index = getIndex(key);
Entry<T, R> current = table[index];
table[index] = new Entry<>(key, value);
if(value == null && current != null && current.getValue() != null) {
size--;
} else if(value != null && (current == null || current.getValue() == null)){
size++;
}
if(current == null && ++internalSize >= (table.length * fillRatio)) {
resizeTable();
}
if(current != null) {
return Optional.ofNullable(current.getValue());
}
return Optional.empty();
}
public Optional<R> get(T key) {
int index = getIndex(key);
Entry<T, R> entry = table[index];
if(entry != null)
return Optional.ofNullable(entry.getValue());
return Optional.empty();
}
public boolean has(T key) {
return get(key).isPresent();
}
public int getSize() {
return size;
}
protected void resizeTable() {
internalSize = size = 0;
Entry<T, R>[] tmp = table;
table = new Entry[(int) ((table.length /fillRatio)* 2)];
for(Entry<T, R> entry : tmp){
if(entry != null) {
put(entry.getKey(), entry.getValue());
}
}
}
private int getIndex(T key) {
int hash = key.hashCode();
int index = (((hash % table.length) + table.length) % table.length);
while(table[index] != null && table[index].getKey().hashCode() != hash) {
if(++index == table.length) {
index = 0;
}
}
return index;
}
public static final class Entry <T, R> {
private final T key;
private final R value;
public Entry(T key, R value) {
this.key = key;
this.value = value;
}
public T getKey() {
return key;
}
public R getValue() {
return value;
}
}
}
it seems to work exactly as expected except for every 100,000 entries or so will return the wrong value for a hash. I can reproduce it fairly reliably with this test
java.util.HashMap<UUID, UUID> javaMap = new java.util.HashMap<>();
HashMap<UUID, UUID> map = new HashMap<>();
for (int i = 0; i < 200000; i++) {
UUID key = UUID.randomUUID(), value = UUID.randomUUID();
javaMap.put(key, value);
map.put(key, value);
}
for (java.util.HashMap.Entry<UUID, UUID> entry : javaMap.entrySet()) {
Optional<UUID> value = map.get(entry.getKey());
assertTrue(value.isPresent());
assertEquals(value.get(), entry.getValue());
}
I'm not seeing what my problem is and I'm not thinking of a good way to debug such a rare occurrence. Any thoughts on what I might be doing wrong or how to debug this without spending forever on it?
How does a Java HashMap handle different objects with the same hash code? answered my question. My HashMap implementation does not handle different objects with the same hash code. It only works correctly if hashcodes are unique to equal objects.

Implementing a remove method in a java Hashmap?

So a hashmap is a hash-based implementation of a map structure in java. I've figured out how to get the hashmap put method to work, but I want to write a method that removes the key value pair, and I'm having trouble implementing it.
The only thing I can really understand right now is how to tell the function to stop in the event that the key is empty or doesn't exist.. I'd love any sort of help. An explanation as to how the method will work, or some basic pseudo-code examples would be much appreciated.
This is what I have in the delete method so far:
public void delete(K key) {
if (key == null) {
throw new IllegalArgumentException("Null Key!");
}
// Implement this method
}
If it helps, here is my completed Map Entry class:
public class MapEntry<K, V> {
MapEntry<K, V> next;
K key;
V value;
public MapEntry(K key, V value) {
this.setKey(key);
this.setValue(value);
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value) {
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setNext(MapEntry<K, V> next) {
this.next = next;
}
public MapEntry<K, V> getNext() {
return next;
}
}
Also, here's the entirety of my HashMap class if it helps.
public class HashMap<K, V> {
private int DEFAULT_CAPACITY = 10;
private MapEntry<K, V>[] Hash;
private int size;
public HashMap() {
Hash = new MapEntry[DEFAULT_CAPACITY];
}
public int getHashCode(K key) {
int bucketIndex = key.hashCode() % Hash.length;
return bucketIndex;
}
public V get(K key) {
if (key == null) {
throw new IllegalArgumentException("Null Key!");
}
MapEntry<K, V> entry = Hash[getHashCode(key)];
while (entry != null && !key.equals(entry.getKey()))
entry = entry.getNext();
if (entry != null)
return entry.getValue();
else
return null;
}
/**
*
* #param key
* #param value
* The put method works by associating the specified value with
* the given key in the map.
* If the key is already in the map,
* the old value is replaced with the new one.
*/
public void put(K key, V value) {
int keyBucket = hash(key);
MapEntry<K, V> temp = Hash[keyBucket];
while (temp != null) {
if ((temp.key == null && key == null)
|| (temp.key != null && temp.key.equals(key))) {
temp.value = value;
return;
}
temp = temp.next;
}
Hash[keyBucket] = new MapEntry<K, V>(key, value);
size++;
}
public void delete(K key) {
if (key == null) {
throw new IllegalArgumentException("Null Key!");
}
// Implement this method
}
public void print(){
//Bonus Method
}
private int hash(K key) {
if (key == null) {
return 0;
} else {
return Math.abs(key.hashCode() % this.Hash.length);
}
} }
Using the same logic that you do in get(), locate the correct bucket and, within that bucket, the correct MapEntry (let's call it e). Then simply remove e from the bucket—basically, this is removing a node from a single-linked list. If e is the first element in the bucket, set the corresponding element of Hash to e.next; otherwise set the next field of the element just before e to e.next. Note that you need one more variable (updated as you're finding e) to keep track of the previous entry in the bucket.
source code
public class MyInternalMap<K, V> implements Map<K, V> {
/**
* https://stackoverflow.com/questions/16266459/implementing-a-remove-method-in-a-java-hashmap
*/
private final int initialCapacity;
private MyMapEntry<K, V>[] mapEntries;
private int size;
public MyInternalMap() {
this(16);
}
public MyInternalMap(int initialCapacity) {
this.initialCapacity = initialCapacity;
mapEntries = new MyMapEntry[initialCapacity];
}
#Override
public int size() {
return size;
}
#Override
public boolean isEmpty() {
return size == 0;
}
#Override
public boolean containsKey(Object key) {
return get(key) != null;
}
#Override
public boolean containsValue(Object value) {
for (int i = 0; i < mapEntries.length; i++) {
MyMapEntry<K, V> mapEntry = mapEntries[i];
if (containsValue(value, mapEntry)) {
return true;
}
}
return false;
}
private boolean containsValue(Object value, MyMapEntry<K, V> mapEntry) {
if (mapEntry == null) {
return false;
}
if (value == mapEntry.getValue() || mapEntry.getValue().equals(value)) {
return true;
}
return containsValue(value, mapEntry.next);
}
#Override
public V get(Object key) {
if (key == null) {
return null;
}
MyMapEntry<K, V> entry = mapEntries[getHashCode(key)];
while (entry != null) {
if (key.equals(entry.key)) {
return entry.value;
}
entry = entry.next;
}
return null;
}
#Override
public V put(K key, V value) {
int keyBucket = getHashCode(key);
MyMapEntry<K, V> temp = mapEntries[keyBucket];
if (temp == null) {
//create new head node in this bucket
mapEntries[keyBucket] = new MyMapEntry<>(key, value);
size++;
return null;
}
while (temp != null) {
if ((temp.key == null && key == null)
|| (temp.key != null && temp.key.equals(key))) {
V returnValue = temp.value;
temp.value = value;
return returnValue;
}
temp = temp.next;
}
//create new node in this bucket
mapEntries[keyBucket].next = new MyMapEntry<>(key, value);
size++;
return null;
}
#Override
public V remove(Object key) {
/**
* Using the same logic that you do in get(), locate the correct bucket and, within that bucket, the correct MapEntry (let's call it e). Then simply remove e from the bucket—basically,
* this is removing a node from a single-linked list. If e is the first element in the bucket, set the corresponding element of Hash to e.next;
* otherwise set the next field of the element just before e to e.next. Note that you need one more variable (updated as you're finding e) to keep track of the previous entry in the bucket
*/
int keyBucket = getHashCode(key);
MyMapEntry<K, V> temp = mapEntries[keyBucket];
if (temp == null)
return null;
MyMapEntry<K, V> prev = temp;
while (temp != null) {
if (temp.key != null && temp.key.equals(key)) {
V valueReturn = temp.value;
if (prev == temp) { //first element?
mapEntries[keyBucket] = temp.next;
} else {
prev.next = temp.next;
}
size--;
return valueReturn;
}
prev = temp;
temp = temp.next;
}
return null;
}
#Override
public void putAll(Map<? extends K, ? extends V> m) {
//TODO impl
}
#Override
public void clear() {
mapEntries = new MyMapEntry[initialCapacity];
size = 0;
}
#Override
public Set<K> keySet() {
Set<K> resultKeys = new HashSet<>();
for (int i = 0; i < mapEntries.length; i++) {
MyMapEntry<K, V> mapEntry = mapEntries[i];
addKeySet(mapEntry, resultKeys);
}
return resultKeys;
}
private void addKeySet(MyMapEntry<K, V> mapEntry, Set<K> resultKeys) {
if (mapEntry != null) {
resultKeys.add(mapEntry.key);
addKeySet(mapEntry.next, resultKeys);
}
}
#Override
public Collection<V> values() {
Collection<V> resultValues = new ArrayList<>();
for (int i = 0; i < mapEntries.length; i++) {
MyMapEntry<K, V> mapEntry = mapEntries[i];
addValue(mapEntry, resultValues);
}
return resultValues;
}
private void addValue(MyMapEntry<K, V> mapEntry, Collection<V> resultValues) {
if (mapEntry != null) {
resultValues.add(mapEntry.value);
addValue(mapEntry.next, resultValues);
}
}
#Override
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> entrySetResult = new HashSet<>();
for (int i = 0; i < mapEntries.length; i++) {
MyMapEntry<K, V> mapEntry = mapEntries[i];
addEntrySet(mapEntry, entrySetResult);
}
return entrySetResult;
}
private void addEntrySet(MyMapEntry<K, V> mapEntry, Set<Entry<K, V>> entrySetResult) {
if (mapEntry != null) {
entrySetResult.add(mapEntry);
addEntrySet(mapEntry.next, entrySetResult);
}
}
private int getHashCode(Object key) {
if (key == null)
return 0;
int bucketIndex = Math.abs(key.hashCode()) % initialCapacity;
return bucketIndex;
}
class MyMapEntry<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
private MyMapEntry<K, V> next;
public MyMapEntry(K key, V value) {
this.key = key;
this.value = value;
}
#Override
public K getKey() {
return key;
}
#Override
public V getValue() {
return value;
}
#Override
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
}
}
public class MyInternalMapTest {
#ParameterizedTest()
#MethodSource({"mapArgumentsProvider"})
public void mapTest(Map<Integer, String> map) {
assertNull(map.get(0));
assertNull(map.get(null));
assertNull(map.remove(0));
assertNull(map.remove(null));
assertNull(map.remove(1));
assertEquals(0, map.size());
assertNull(map.put(1, "1"));
assertEquals(1, map.size());
assertEquals("1", map.put(1, "2"));
assertEquals(1, map.size());
assertEquals("2", map.get(1));
assertEquals(1, map.size());
assertNull(map.put(2, "3"));
assertEquals(2, map.size());
assertEquals("2", map.remove(1));
assertEquals(1, map.size());
assertNull(map.remove(1));
assertEquals("3", map.remove(2));
assertEquals(0, map.size());
}
#ParameterizedTest()
#MethodSource({"mapArgumentsProvider"})
public void mapSameHashCodeTest(Map<Integer, String> map) {
assertNull(map.put(1, "1"));
assertEquals("1", map.get(1));
assertNull(map.put(17, "2"));
assertEquals("1", map.get(1));
assertEquals("2", map.get(17));
assertEquals("1", map.get(1));
assertTrue(map.containsValue("1"));
assertTrue(map.containsValue("2"));
assertFalse(map.containsValue("3"));
assertEquals(Arrays.asList("1", "2"), map.values().stream().sorted().collect(Collectors.toList()));
}
private static Stream<Arguments> mapArgumentsProvider() {
return Stream.of(
Arguments.of(new MyInternalMap<>()),
Arguments.of(new HashMap<>())
);
}
}

Categories

Resources