Use LinkedHashMap to implement LRU cache - java

I was trying to implement a LRU cache using LinkedHashMap.
In the documentation of LinkedHashMap (http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html), it says:
Note that insertion order is not affected if a key is re-inserted into the map.
But when I do the following puts
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private int size;
public static void main(String[] args) {
LRUCache<Integer, Integer> cache = LRUCache.newInstance(2);
cache.put(1, 1);
cache.put(2, 2);
cache.put(1, 1);
cache.put(3, 3);
System.out.println(cache);
}
private LRUCache(int size) {
super(size, 0.75f, true);
this.size = size;
}
#Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > size;
}
public static <K, V> LRUCache<K, V> newInstance(int size) {
return new LRUCache<K, V>(size);
}
}
The output is
{1=1, 3=3}
Which indicates that the re-inserted did affected the order.
Does anybody know any explanation?

As pointed out by Jeffrey, you are using accessOrder. When you created the LinkedHashMap, the third parameter specify how the order is changed.
"true for access-order, false for insertion-order"
For more detailed implementation of LRU, you can look at this
http://www.programcreek.com/2013/03/leetcode-lru-cache-java/

But you aren't using insertion order, you're using access order.
order of iteration is the order in which its entries were last
accessed, from least-recently accessed to most-recently (access-order)
...
Invoking the put or get method results in an access to the
corresponding entry
So this is the state of your cache as you modify it:
LRUCache<Integer, Integer> cache = LRUCache.newInstance(2);
cache.put(1, 1); // { 1=1 }
cache.put(2, 2); // { 1=1, 2=2 }
cache.put(1, 1); // { 2=2, 1=1 }
cache.put(3, 3); // { 1=1, 3=3 }

Here is my implementation by using LinkedHashMap in AccessOrder. It will move the latest accessed element to the front which only incurs O(1) overhead because the underlying elements are organized in a doubly-linked list while also are indexed by hash function. So the get/put/top_newest_one operations all cost O(1).
class LRUCache extends LinkedHashMap<Integer, Integer>{
private int maxSize;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
this.maxSize = capacity;
}
//return -1 if miss
public int get(int key) {
Integer v = super.get(key);
return v == null ? -1 : v;
}
public void put(int key, int value) {
super.put(key, value);
}
#Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return this.size() > maxSize; //must override it if used in a fixed cache
}
}

Technically LinkedHashMap has the following constructor. Which help us to make the access-order True/False. If it is false then it keep the insertion-order.
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
(#Constructs an empty LinkedHashMap instance with the specified initial capacity, load factor and ordering mode)
Following is the simple implementation of LRU Cache ---
class LRUCache {
private LinkedHashMap<Integer, Integer> linkHashMap;
public LRUCache(int capacity) {
linkHashMap = new LinkedHashMap<Integer, Integer>(capacity, 0.75F, true) {
#Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
};
}
public void put(int key, int value) {
linkHashMap.put(key, value);
}
public int get(int key) {
return linkHashMap.getOrDefault(key, -1);
}
}

I used the following code and its works!!!!
I have taken window size to be 4, but any value can be taken.
for Insertion order:
1: Check if the key is present.
2: If yes, then remove it (by using lhm.remove(key))
3: Add the new Key Value pair.
for Access Order:
No need of removing keys only put and get statements do everything automatically.
This code is for ACCESS ORDER:
import java.util.LinkedHashMap;
public class LRUCacheDemo {
public static void main(String args[]){
LinkedHashMap<String,String> lhm = new LinkedHashMap<String,String>(4,0.75f,true) {
#Override
protected boolean removeEldestEntry(Map.Entry<String,String> eldest) {
return size() > 4;
}
};
lhm.put("test", "test");
lhm.put("test1", "test1");
lhm.put("1", "abc");
lhm.put("test2", "test2");
lhm.put("1", "abc");
lhm.put("test3", "test3");
lhm.put("test4", "test4");
lhm.put("test3", "test3");
lhm.put("1", "abc");
lhm.put("test1", "test1");
System.out.println(lhm);
}
}

I also implement LRU cache with little change in code. I have tested and it works perfectly as concept of LRU cache.
package com.first.misc;
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCachDemo {
public static void main(String aa[]){
LRUCache<String, String> lruCache = new LRUCache<>(3);
lruCache.cacheable("test", "test");
lruCache.cacheable("test1", "test1");
lruCache.cacheable("test2", "test2");
lruCache.cacheable("test3", "test3");
lruCache.cacheable("test4", "test4");
lruCache.cacheable("test", "test");
System.out.println(lruCache.toString());
}
}
class LRUCache<K, T>{
private Map<K,T> cache;
private int windowSize;
public LRUCache( final int windowSize) {
this.windowSize = windowSize;
this.cache = new LinkedHashMap<K, T>(){
#Override
protected boolean removeEldestEntry(Map.Entry<K, T> eldest) {
return size() > windowSize;
}
};
}
// put data in cache
public void cacheable(K key, T data){
// check key is exist of not if exist than remove and again add to make it recently used
// remove element if window size is exhaust
if(cache.containsKey(key)){
cache.remove(key);
}
cache.put(key,data);
}
// evict functioanlity
#Override
public String toString() {
return "LRUCache{" +
"cache=" + cache.toString() +
", windowSize=" + windowSize +
'}';
}
}

Related

Hashmap using lists as a buffer

I need to create a hashmap that can store multiple values for one key, I know multimaps could do this but I also need to keep those value lists to a specific length. I need a each key to store a list of n values with those being the latest n values, i.e if i reached length n and I add another value the first one added will drop off the value list and the target length is maintained.
I started off the below code and want to change it so i can store/add to a list for a specific key.
public static void main(final String args[]) throws Exception {
final int maxSize = 4;
final LinkedHashMap<String, String> cache = new LinkedHashMap<String, String>() {
#Override
protected boolean removeEldestEntry(final Map.Entry eldest) {
return size() > maxSize;
}
};
cache.put("A", "A");
System.out.println(cache);
cache.put("B", "A");
System.out.println(cache);
cache.put("C", "A");
System.out.println(cache);
cache.put("D", "A");
System.out.println(cache);
cache.put("E", "A");
System.out.println(cache);
cache.put("F", "A");
System.out.println(cache);
cache.put("G", "A");
}
Output:
{A=A}
{A=A, B=A}
{A=A, B=A, C=A}
{A=A, B=A, C=A, D=A}
{B=A, C=A, D=A, E=A}
{C=A, D=A, E=A, F=A}
I tried changing it to sth like this but can't get it working (python guy here who is getting started with java)
public LinkedHashMap filteroutliers(final String arg, final long arg2) throws Exception{
final int bufferSize = 5;
final LinkedHashMap<Integer, ArrayList<Double>> bufferList = new LinkedHashMap<Integer, ArrayList<Double>>(){
#Override
protected boolean removeEldestEntry(final Map.Entry eldest){
return size() < bufferSize;
}
};
return bufferList;
}
You can extend the HashMap and have your custom map something like this, here I maintained a queue to store the keys so when the limit reaches you can remove the earliest key-value pair (FIFO)
class CacheMap<K, V> extends HashMap<K, V> {
private static final long serialVersionUID = 1L;
private int MAX_SIZE;
private Queue<K> queue = new LinkedList<>();
public CacheMap(int capacity) {
super();
MAX_SIZE = capacity;
}
#Override
public V put(K key, V value) {
if (super.size() < MAX_SIZE) {
queue.add(key);
} else {
super.remove(queue.poll());
}
super.put(key, value);
return value;
}
}
}

How to add all integers for duplicates elements in HashMap?

I have the following HashMap:
Map<String, Integer> map = new HashMap<>();
How can I sum up all the integers for the duplicates String? or is there a better way to do it using Set?
for example, if I add these elements:
car 100
TV 140
car 5
charger 10
TV 10
I want the list to have:
car 105
TV 150
charger 10
I believe your question is: how do I put key/value pairs into a map in a way that changes the value rather than replacing it, for the same key.
Java has a Map method specifically for this purpose:
map.merge(key, value, (v, n) -> v + n);
This will add the value if the key isn't in the map. Otherwise it'll replace the current value with the sum of the current and new values.
The merge method was introduced in Java 8.
First of all you cannot add duplicate keys in map.
But if I understood what you want, the below code may help you:
if (map.containsKey(key))
map.put(key, map.get(key) + newValue);
else
map.put(key, newValue);
For java-8 and higher
You may just want to use the Map#merge method. It is the easiest way possible. If the key does not exist, it will add it, if it does exist, it will perform the merge operation.
map.merge("car", 100, Integer::sum);
map.merge("car", 20, Integer::sum);
System.out.println(map); // {car=120}
When you add "TV" for the second time, the first value (140) will be override because you cannot have duplicated keys on Map implementation. If you want to increment the value you will need to check if the key "TV" already exists and then increment/add the value.
For example:
if (map.containsKey(key)) {
value += map.get(key);
}
map.put(key, value)
HashMap dosen't save duplicates keys!
You can extend the HashMap Class(JAVA >= 8):
public class MyHashMap2 extends HashMap<String, Integer>{
#Override
public Integer put(String key, Integer value) {
return merge(key, value, (v, n) -> v + n);
}
public static void main (String[] args) throws java.lang.Exception
{
MyHashMap2 list3=new MyHashMap2();
list3.put("TV", 10);
list3.put("TV", 20);
System.out.println(list3);
}
}
Or You can aggregate the HashMap and replace the put method to add to the previous value the new value.
HashMap<String, Integer> list = new HashMap<>();
list.put("TV", 10);
list.put("TV", 20);
System.out.println(list);
MyHashMap list2 = new MyHashMap();
list2.put("TV", 10);
list2.put("TV", 20);
System.out.println(list2);
//OUTPUT:
//{TV=20}
//MyHashMap [List={TV=30}]
public class MyHashMap implements Map<String, Integer>{
HashMap<String, Integer> list = new HashMap<>();
public MyHashMap() {
super();
}
#Override
public int size() {
return list.size();
}
#Override
public boolean isEmpty() {
return list.isEmpty();
}
#Override
public boolean containsKey(Object key) {
return list.containsKey(key);
}
#Override
public boolean containsValue(Object value) {
return list.containsValue( value);
}
#Override
public Integer get(Object key) {
return list.get(key);
}
#Override
public Integer put(String key, Integer value) {
if(list.containsKey(key))
list.put(key, list.get(key)+value);
else
list.put(key, value);
return value;
}
#Override
public Integer remove(Object key) {
return list.remove(key);
}
#Override
public void putAll(Map<? extends String, ? extends Integer> m) {
list.putAll(m);
}
#Override
public void clear() {
list.clear();
}
#Override
public Set<String> keySet() {
return list.keySet();
}
#Override
public Collection<Integer> values() {
return list.values();
}
#Override
public Set<java.util.Map.Entry<String, Integer>> entrySet() {
return list.entrySet();
}
#Override
public String toString() {
return "MyHashMap [list=" + list + "]";
}
}
you can try the code here:https://ideone.com/Wl4Arb

Hashmap with a .class object as a key

I've created a hashmap with .class objects for keys.
Hashmap<Class<? extends MyObject>, Object> mapping = new Hashmap<Class<? extends MyObject>, Object>();
This is all well and fine, but I'm getting strange behaviour that I can only attribute to strangeness with the hash function. Randomly during runtime, iterating through the hashmap will not hit every value; it will miss one or two. I think this may be due to the .class object not being final, and therefore it changes causing it to map to a different hash value. With a different hash value, the hashmap wouldn't be able to correctly correlate the key with the value, thus making it appear to have lost the value.
Am I correct that this is what is going on? How can I work around this? Is there a better way to accomplish this form of data structure?
Edit: I really thought I was onto something with the hash function thing, but I'll post my real code to try and figure this out. It may be a problem with my implementation of a multimap. I've been using it for quite some time and haven't noticed any issues until recently.
/**
* My own implementation of a map that maps to a List. If the key is not present, then
* the map adds a List with a single entry. Every subsequent addition to the key
* is appended to the List.
* #author
*
* #param <T> Key
* #param <K> Value
*/
public class MultiMap<T, K> implements Map<T, List<K>>, Serializable, Iterable<K> {
/**
*
*/
private static final long serialVersionUID = 5789101682525659411L;
protected HashMap<T, List<K>> set = new HashMap<T, List<K>>();
#Override
public void clear() {
set = new HashMap<T, List<K>>();
}
#Override
public boolean containsKey(Object arg0) {
return set.containsKey(arg0);
}
#Override
public boolean containsValue(Object arg0) {
boolean output = false;
for(Iterator<List<K>> iter = set.values().iterator();iter.hasNext();) {
List<K> searchColl = iter.next();
for(Iterator<K> iter2 = searchColl.iterator(); iter2.hasNext();) {
K value = iter2.next();
if(value == arg0) {
output = true;
break;
}
}
}
return output;
}
#Override
public Set<Entry<T, List<K>>> entrySet() {
Set<Entry<T, List<K>>> output = new HashSet<Entry<T,List<K>>>();
for(Iterator<T> iter1 = set.keySet().iterator(); iter1.hasNext();) {
T key = iter1.next();
for(Iterator<K> iter2 = set.get(key).iterator(); iter2.hasNext();) {
K value = iter2.next();
List<K> input = new ArrayList<K>();
input.add(value);
output.add(new AbstractMap.SimpleEntry<T,List<K>>(key, input));
}
}
return output;
}
#Override
public boolean isEmpty() {
return set.isEmpty();
}
#Override
public Set<T> keySet() {
return set.keySet();
}
#Override
public int size() {
return set.size();
}
#Override
public Collection<List<K>> values() {
Collection<List<K>> values = new ArrayList<List<K>>();
for(Iterator<T> iter1 = set.keySet().iterator(); iter1.hasNext();) {
T key = iter1.next();
values.add(set.get(key));
}
return values;
}
#Override
public List<K> get(Object key) {
return set.get(key);
}
#Override
public List<K> put(T key, List<K> value) {
return set.put(key, value);
}
public void putValue(T key, K value) {
if(set.containsKey(key)) {
set.get(key).add(value);
}
else {
List<K> setval = new ArrayList<K>();
setval.add(value);
set.put(key, setval);
}
}
#Override
public List<K> remove(Object key) {
return set.remove(key);
}
public K removeValue(Object value) {
K valueRemoved = null;
for(T key:this.keySet()) {
for(K val:this.get(key)) {
if(val.equals(value)) {
List<K> temp = this.get(key);
temp.remove(value);
valueRemoved = val;
this.put(key, temp);
}
}
}
return valueRemoved;
}
#Override
public void putAll(Map<? extends T, ? extends List<K>> m) {
for(Iterator<? extends T> iter = m.keySet().iterator(); iter.hasNext();) {
T key = iter.next();
set.put(key, m.get(key));
}
}
#Override
public Iterator<K> iterator() {
return new MultiMapIterator<K>(this);
}
}
Perhaps there is an issue with my iterator? I'll post that code as well.
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class MultiMapIterator<T> implements Iterator<T> {
private MultiMap <?, T> map;
private Iterator<List<T>> HashIter;
private Iterator<T> govIter;
private T value;
public MultiMapIterator(MultiMap<?, T> map) {
this.map = map;
HashIter = map.values().iterator();
if(HashIter.hasNext()) {
govIter = HashIter.next().iterator();
}
if(govIter.hasNext()) {
value = govIter.next();
}
}
#Override
public boolean hasNext() {
if (govIter.hasNext()) {
return true;
}
else if(HashIter.hasNext()) {
govIter = HashIter.next().iterator();
return this.hasNext();
}
else {
return false;
}
}
#Override
public T next() {
if(!this.hasNext()) {
throw new NoSuchElementException();
}
else {
value = govIter.next();
return value;
}
}
#Override
public void remove() {
map.remove(value);
}
}
Sorry for the long tracts of code. Thank you for spending time helping me with this.
You pull the a value out of govIter in the constructor, but never return it.
Your iterator remove method is completely wrong. You are iterating values, but calling the map.remove which removes by key. you simply want to call govIter.remove() (unless you need to avoid empty lists, in which case it's more complicated).
Your hasNext() method could also have problems depending on whether or not you allow empty Lists values in your multimap.

How to declare final HashMap that should not allow to update or remove element

I have a hash map like,
public static void main(String[] args) {
final Map<String, String> daysMap = new HashMap(7);
daysMap.put("1", "Sunday");
daysMap.put("2", "Monday");
daysMap.put("3", "Tuesday");
daysMap.put("4", "Wednesday");
daysMap.put("5", "Thursday");
daysMap.put("6", "Friday");
daysMap.put("7", "Saturday");
}
In this map
1. Should not allow to put more than 7 elements
2. Should not update value for corresponding key [like daysMap.put("5", "xxx");]
3. Should not allow to remove any key
How to do?
You can implement a new HashMap
public class CoolMap<K, V> extends HashMap<K, V> {
#Override
public V put(K key, V value) {
if (size() == 7) {
throw new IllegalStateException("Size is at max!");
} else {
// If there is something already with that key
if (containsKey(value)) {
// do nothing
return value;
} else {
// put inside
return super.put(key, value);
}
}
}
#Override
public void putAll(Map<? extends K, ? extends V> collection) {
if (collection.size() > 7) {
throw new IllegalStateException("Size is at max!");
} else {
super.putAll(collection);
}
}
#Override
public V remove(Object key) {
return null;// doesn't remove anything
}
Points 2 and 3 are covered by Collections.unmodifiableMap. To cover the first point, you can add an hand written test.
As has been already discussed ,the Points 2 and 3 are covered like this
import java.util.*;
public class OP2 {
public static void main(String[] s) {
//object hash table
Hashtable<String,String> table = new Hashtable<String,String>();
table.
// populate the table
table.put("1", "Sunday");
table.put("2", "Monday");
table.put("3", "Tuesday");
table.put("4", "Wednesday");
table.put("5", "Thursday");
table.put("6", "Friday");
table.put("7", "Saturday");
System.out.println("Initial collection: "+table);
// create unmodifiable map
Map m = Collections.unmodifiableMap(table);
// try to modify the collection
// m.put("key3", "value3");
//Uncomment the above line and an error is obtained
}
}
Moreover for the first problem it would be better to call a function when you populate your map:-
public boolean putAndTest(MyKey key, MyValue value) {
if (map.size() >= MAX && !map.containsKey(key)) {
return false;
} else {
map.put("Whatever you want");
return true;
}
}
Why donĀ“t you create your own object that contains a private hashmap and then you can allow what methods on that private hashmap that are made public?

A sorted ComputingMap?

How can I construct a SortedMap on top of Guava's computing map (or vice versa)? I want the sorted map keys as well as computing values on-the-fly.
The simplest is probably to use a ConcurrentSkipListMap and the memoizer idiom (see JCiP), rather than relying on the pre-built unsorted types from MapMaker. An example that you could use as a basis is a decorator implementation.
May be you can do something like this.It's not a complete implementation.Just a sample to convey the idea.
public class SortedComputingMap<K, V> extends TreeMap<K, V> {
private Function<K, V> function;
private int maxSize;
public SortedComputingMap(int maxSize, Function<K, V> function) {
this.function = function;
this.maxSize = maxSize;
}
#Override
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
#Override
public void putAll(Map<? extends K, ? extends V> map) {
throw new UnsupportedOperationException();
}
#Override
public V get(Object key) {
V tmp = null;
K Key = (K) key;
if ((tmp = super.get(key)) == null) {
super.put(Key, function.apply(Key));
}
if (size() > maxSize)
pollFirstEntry();
return tmp;
}
public static void main(String[] args) {
Map<Integer, Long> sortedMap = new SortedComputingMap<Integer, Long>(3,
new Function<Integer, Long>() {
#Override
public Long apply(Integer n) {
Long fact = 1l;
while (n != 0)
fact *= n--;
return fact;
}
});
sortedMap.get(12);
sortedMap.get(1);
sortedMap.get(2);
sortedMap.get(5);
System.out.println(sortedMap.entrySet());
}
}
If you need the thread safety, this could be tricky, but if you don't I'd recommend something close to Emil's suggestion, but using a ForwardingSortedMap rather than extending TreeMap directly.

Categories