This question already has answers here:
Sort a Map<Key, Value> by values
(64 answers)
Closed 6 years ago.
I have a TreeMap and a working by key Comperator:
Map<String, Long> movieReviewsTreeMap = new TreeMap<String, Long>(new MyComperator());
class MyComperator implements Comparator<String>{
#Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
}
I'm trying to modify the Comparator in order to compare TreeMap entries.
My goal is to sort TreeMap by value (highest to lowest), and by key in case two values are equal.
Thanks.
You can sort only keys in java.util.TreeMap but you can extend it and override methods where comparator is used to implement some addiditional logic.
You can try some what likewise, to full fill you requirement..
import java.util.*;
import java.util.Map.Entry;
public class TestCollection13{
public static void main(String args[]){
Map<MyComperator, Long> movieReviewsTreeMap = new TreeMap<MyComperator, Long>(new MyComperator());
Long l1 = new Long(5);
movieReviewsTreeMap.put(new MyComperator("a", l1),l1);
Long l2 = new Long(2);
movieReviewsTreeMap.put(new MyComperator("a", l2),l2);
Long l3 = new Long(6);
movieReviewsTreeMap.put(new MyComperator("a", l3),l3);
Long l4 = new Long(0);
movieReviewsTreeMap.put(new MyComperator("a", l4),l4);
for(Entry<MyComperator, Long> entry : movieReviewsTreeMap.entrySet()){
System.out.println("Key : " + entry.getKey().getKey() + " , value :" + entry.getValue());
}
}
}
class MyComperator implements Comparator<MyComperator>{
String key;
Long value;
MyComperator(){
}
MyComperator(String key, Long value){
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
#Override
public int hashCode() {
return this.key.length();
}
#Override
public boolean equals(Object obj) {
MyComperator mc = (MyComperator)obj;
if(mc.getKey().equals(this.getKey()) && mc.getValue().equals(this.getValue())){
return false;
}
// TODO Auto-generated method stub
return super.equals(obj);
}
#Override
public int compare(MyComperator o1, MyComperator o2) {
if(o1.getKey().equals(o2.getKey())){
return o1.getValue().compareTo(o2.getValue());
}
return 0;
}
}
Answer :
Key : a , value :0
Key : a , value :2
Key : a , value :5
Key : a , value :6
Related
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
Does a java map implementation exist where the keys are known, however the values should only be computed on the first access as calculating the values is expensive.
The following demonstrates how I would like it to work.
someMap.keySet(); // Returns all keys but no values are computed.
someMap.get(key); // Returns the value for key computing it if needed.
The reason for this is I have something which holds a bunch of data and this Object returns the data as a Map<String, String> this is computationally heavy to compute because computing the values is expensive, the keys are however cheap to compute.
The Map must maintain its type so I can't return a Map<String, Supplier<String>>. The returned Map may be returned as read only.
The map itself could be created by passing in both a Set<String> defining the keys and a Function<String, String> which given a key returns its value.
One solution could be to have a Map that takes a Set of keys and a Function which given a key can compute the value.
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
/**
* Create a Map where we already know the keys but computing the values is expensive and so is delayed as
* much as possible.
*
*/
#AllArgsConstructor
public class MapWithValuesProvidedByFunction implements Map<String, String> {
/**
* All keys that are defined.
*/
private Set<String> keys;
/**
* A function which maps a key to its value.
*/
private Function<String, String> mappingFunction;
/**
* Holds all keys and values we have already computed.
*/
private final Map<String, String> computedValues = new HashMap<>();
#Override
public int size() {
return keys.size();
}
#Override
public boolean isEmpty() {
return keys.isEmpty();
}
#Override
public boolean containsKey(Object key) {
return keys.contains(key);
}
#Override
public boolean containsValue(Object value) {
if(computedValues.size() == keys.size()) return computedValues.containsValue(value);
for(String k : keys) {
String v = get(k);
if(v == value) return true;
if(v != null && v.equals(value)) return true;
}
return false;
}
#Override
public String get(Object key) {
if(keys.contains(key)) {
return computedValues.computeIfAbsent(key.toString(), mappingFunction);
}
return null;
}
#Override
public String put(String key, String value) {
throw new UnsupportedOperationException("Not modifiable");
}
#Override
public String remove(Object key) {
throw new UnsupportedOperationException("Not modifiable");
}
#Override
public void putAll(Map<? extends String, ? extends String> m) {
throw new UnsupportedOperationException("Not modifiable");
}
#Override
public void clear() {
throw new UnsupportedOperationException("Not modifiable");
}
#Override
public Set<String> keySet() {
return Collections.unmodifiableSet(keys);
}
#Override
public Collection<String> values() {
return keys.stream().map(this::get).collect(Collectors.toList());
}
#Override
public Set<java.util.Map.Entry<String, String>> entrySet() {
Set<Entry<String, String>> set = new HashSet<>();
for(String s : keys) {
set.add(new MyEntry(s, this::get));
}
return set;
}
#AllArgsConstructor
#EqualsAndHashCode
public class MyEntry implements Entry<String, String> {
private String key;
private Function<String, String> valueSupplier;
#Override
public String getKey() {
return key;
}
#Override
public String getValue() {
return valueSupplier.apply(key);
}
#Override
public String setValue(String value) {
throw new UnsupportedOperationException("Not modifiable");
}
}
}
An example of this being used might be:
Map<String, String> map = new MapWithValuesProvidedByFunction(
Set.of("a", "b", "c"), // The known keys
k -> "Slow to compute function"); // The function to make the values
Changing this to be generic should be easy enough.
I suspect a better solution exists, however this might be good enough for someone else.
You could do something like this. The map has a key and a Fnc class which holds a function and the argument to the function.
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class MapDemo {
public static Map<String, Object> mymap = new HashMap<>();
public static void main(String[] args) {
MapDemo thisClass = new MapDemo();
// populate the functions
mymap.put("v1", new Fnc<String>(String::toUpperCase));
mymap.put("10Fact", new Fnc<Integer>((Integer a) -> {
int f = 1;
int k = a;
while (k-- > 1) {
f *= k;
}
return f + "";
}));
mymap.put("expensive",
new Fnc<Integer>(thisClass::expensiveFunction));
// access them - first time does the computation
System.out.println(getValue("expensive", 1000));
System.out.println(getValue("10Fact", 10));
System.out.println(getValue("v1", "hello"));
// second time does not.
System.out.println(getValue("expensive"));
System.out.println(getValue("10Fact"));
System.out.println(getValue("v1"));
}
public String expensiveFunction(int q) {
return q * 100 + ""; // example
}
static class Fnc<T> {
Function<T, String> fnc;
public Fnc(Function<T,String> fnc) {
this.fnc = fnc;
}
}
public <T> void addFunction(String key,
Function<String, T> fnc) {
mymap.put(key, fnc);
}
public static String getValue(String key) {
Object ret = mymap.get(key);
if (ret instanceof Fnc) {
return null;
}
return (String)mymap.get(key);
}
public static <T> String getValue(String key, T arg) {
Object ret = mymap.get(key);
if (ret instanceof Fnc) {
System.out.println("Calculating...");
ret = ((Fnc)ret).fnc.apply(arg);
mymap.put(key, ret);
}
return (String) ret;
}
}
First time thru, the function is called and the value is computed, stored, and returned. Subsequent calls return the stored value.
Note that the value replaces the computing function.
I have implemented Dictionary with Vector(Array). In array i store a String data. Now i have get position Method. But i want to retrieve data at some position. what will be the method? Thank you.
private int findpositionProfile(String smkey){
DictionaryProfile p = new DictionaryProfile(smkey,null);
return data.getposi(p);
}
public Profile getProfile(int i){
// DictionaryProfile p = new DictionaryProfile(null,null);
return data.get(i);
this is not working
public class Dictionary {
private Vector data;
private Vector data1;
public Dictionary() {
data = new Vector(100);
data1 = new Vector(100);
}
public void addProfile(String smkey, Profile smvalue) {
DictionaryProfile d = new DictionaryProfile(smkey, smvalue);
if (data.getposi(d) == -1) {
data.addLast(d);
}
data.replace(d);
}
public void addCorporate(String smkey, CorporateProfile smvalue) {
DictionaryCorporate d = new DictionaryCorporate(smkey, smvalue);
if (data1.getposi(d) == -1) {
data1.addLast(d);
}
data1.replace(d);
}
private int findpositionProfile(String smkey) {
DictionaryProfile p = new DictionaryProfile(smkey,null);
return data.getposi(p);
}
public CorporateProfile getCorporate(int i){
return data.get(i);
}
public Profile getProfile(int i){
DictionaryProfile p = new DictionaryProfile(null,null);
return data.get(i);
}
My dictionaryPair::
public class DictionaryProfile implements Comparable
{
private String userName ;
private Profile completeProfile ;
public DictionaryProfile ( String name,Profile p){
userName = name;
completeProfile = p;
}
public String getUserName(){
return userName;
}
public Profile getProfile(){
return completeProfile;
}
public void setUsename ( String newname ){
userName= newname;
}
public void setProfile ( Profile pro ){
completeProfile = pro;
}
public int compareTo(Object obj){
DictionaryProfile dp = (DictionaryProfile) obj;
return (this.getUserName()).compareTo(dp.getUserName());
}
}
No one should be using the JDK 1.0 vintage Vector class. This doesn't look like a generic Dictionary ADT to me.
This method makes no sense whatsoever:
public Profile getProfile(int i){
DictionaryProfile p = new DictionaryProfile(null,null);
return data.get(i);
}
The local variable p is instantiated, never used, and eligible for GC when it goes out of scope. Data is a Vector holding type Object. Where do you expect to get a Profile from?
This code makes no sense.
This will work, unless you pass an index that's out of bounds.
public Profile getProfile(int i){
return (Profile) data.get(i);
}
None of this describes how a Dictionary works. It's a synonym for a Map, which has a key/value pair. Your code isn't doing that. Doesn't use generics for key or value. Why would you do this instead of just using a Map<K, V>?
I think you should start with this:
package collections;
public interface Dictionary<K, V> {
V get(K key);
V put(K key, V value);
boolean containsKey(K key);
int size();
}
Your keys ought to be immutable.
This is what I would consider the minimal interface for a proper Dictionary.
Here's an implementation that uses backing ArrayList:
package collections;
import java.util.ArrayList;
import java.util.List;
/**
* Implementation of a Dictionary interface
* Created by Michael
* Creation date 12/30/2015.
* #link https://stackoverflow.com/questions/34538520/data-structures-and-algorithms-implementation-dictionary/34538668?noredirect=1#comment56819702_34538668
*/
public class DictionaryImpl<K, V> implements Dictionary<K, V> {
private List<K> keys;
private List<V> values;
public DictionaryImpl() {
this.keys = new ArrayList<>();
this.values = new ArrayList<>();
}
#Override
public V get(K key) {
V value = null;
if (this.keys.contains(key)) {
int index = this.getIndex(key);
if (index != -1) {
value = this.values.get(index);
}
}
return value;
}
#Override
public V put(K key, V value) {
V previousValue = null;
if (this.keys.contains(key)) {
previousValue = this.get(key);
}
this.keys.add(key);
this.values.add(value);
return previousValue;
}
#Override
public boolean containsKey(K key) {
return this.keys.contains(key);
}
#Override
public int size() {
return this.keys.size();
}
private int getIndex(K keyToFind) {
int index = -1;
if (this.keys.contains(keyToFind)) {
for (K key : this.keys) {
++index;
if (key.equals(keyToFind)) {
break;
}
}
}
return index;
}
}
Here's a Junit test to prove that it's all working:
package collections;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Junit test for Dictionary
* Created by Michael
* Creation date 12/30/2015.
* #link https://stackoverflow.com/questions/34538520/data-structures-and-algorithms-implementation-dictionary/34538668?noredirect=1#comment56819702_34538668
*/
public class DictionaryTest {
private Dictionary<String, Integer> testDictionary;
#Before
public void setUp() {
this.testDictionary = new DictionaryImpl<>();
this.testDictionary.put("foo", 17);
this.testDictionary.put("bar", 23);
this.testDictionary.put("baz", 31);
this.testDictionary.put("bat", 41);
}
#Test
public void testContainsKey_True() {
String [] keys = { "foo", "bar", "baz", "bat" };
for (String key : keys) {
Assert.assertTrue(String.format("Should have contained key '%s'", key), this.testDictionary.containsKey(key));
}
}
#Test
public void testContainsKey_False() {
String [] keys = { "dopey", "sleepy", "doc", "sneezy" };
for (String key : keys) {
Assert.assertTrue(String.format("Should not have contained key '%s'", key), !this.testDictionary.containsKey(key));
}
}
#Test
public void testGet_Success() {
String [] keys = { "foo", "bar", "baz", "bat" };
Integer [] values = { 17, 23, 31, 41 };
for (int i = 0; i < keys.length; ++i) {
Assert.assertEquals(String.format("Should have returned value %d for key '%s'", values[i], keys[i]), values[i], this.testDictionary.get(keys[i]));
}
}
#Test
public void testGet_NoSuchKey() {
String [] keys = { "dopey", "sleepy", "doc", "sneezy" };
for (String key : keys) {
Assert.assertNull(String.format("Should have returned null for key '%s'", key), this.testDictionary.get(key));
}
}
#Test
public void testSize() {
int expected = 4;
Assert.assertEquals(expected, this.testDictionary.size());
}
}
I have a distributed map and I want to find the lowest or highest key (an object implementing compareable). What is the most efficient way to get those keys? I mean something like every node provides his lowest key and in the end the lowest key is the lowest of every node.
So I think:
MyObj max = Collections.max(map.keySet());
is not the most efficient way. And if I want to use
new DistributedTask<>(new Max(input), key);
I would need to now the key and therefore fetch all Keys over wire. I think in that case I could do Collections.max(map.keySet()); as well.
Hmm ... any ideas?
You could use EntryProcessor.executeOnEntries - with a stateful EntryProcessor - and then let the it do all the work for you; have each key map to a sentinel MIN and MAX enum if they are the min and max.
If you have some idea of the bounds, you could attach a filter Predicate as well to speed it up that way, too.
This map reduce solution seems to have a lot of overhead but it is the best way I could get the job done. Any better ideas are still welcome.
public static void main(String[] args) throws ExecutionException, InterruptedException {
IMap<String, Integer> map = instance.getMap("test");
JobTracker jobTracker = instance.getJobTracker( "default" );
KeyValueSource<String, Integer> source = KeyValueSource.fromMap( map );
Job<String, Integer> job = jobTracker.newJob(source);
JobCompletableFuture<Map<String, String>> future = job
.mapper(new MaxMapper())
.reducer(new MaxReducerFactory())
.submit();
System.out.println("mr max: " + future.get());
}
public static class MaxMapper implements Mapper<String, Integer, String, String> {
private volatile String max = null;
#Override
public void map(String s, Integer integer, Context<String, String> ctx) {
if (max == null || s.compareTo(max)>0) {
max = s;
ctx.emit("max", max);
}
}
}
public static class MaxReducerFactory implements ReducerFactory<String,String,String> {
#Override
public Reducer<String, String> newReducer(String s) {
return new MaxReducer();
}
private class MaxReducer extends Reducer<String, String> {
private volatile String max = null;
#Override
public void reduce(String s) {
if (max == null || s.compareTo(max)>0) max = s;
}
#Override
public String finalizeReduce() {
return max; // == null ? "" : max;
}
}
}
Mapper:
import com.hazelcast.mapreduce.Context;
import com.hazelcast.mapreduce.Mapper;
import stock.Stock;
public class MinMaxMapper implements Mapper<String, Stock, String, Double> {
static final String MIN = "min";
static final String MAX = "max";
#Override
public void map(String key, Stock value, Context<String, Double> context) {
context.emit(MIN, value.getPrice());
context.emit(MAX, value.getPrice());
}
}
Combiner:
import com.hazelcast.mapreduce.Combiner;
import com.hazelcast.mapreduce.CombinerFactory;
public class MinMaxCombinerFactory implements CombinerFactory<String, Double, Double> {
#Override
public Combiner<Double, Double> newCombiner(String key) {
return new MinMaxCombiner(MinMaxMapper.MAX.equals(key) ? true : false);
}
private static class MinMaxCombiner extends Combiner<Double, Double> {
private final boolean maxCombiner;
private double value;
private MinMaxCombiner(boolean maxCombiner) {
this.maxCombiner = maxCombiner;
this.value = maxCombiner ? -Double.MAX_VALUE : Double.MAX_VALUE;
}
#Override
public void combine(Double value) {
if (maxCombiner) {
this.value = Math.max(value, this.value);
} else {
this.value = Math.min(value, this.value);
}
}
#Override
public Double finalizeChunk() {
return value;
}
#Override
public void reset() {
this.value = maxCombiner ? -Double.MAX_VALUE : Double.MAX_VALUE;
}
}
}
Reducer:
import com.hazelcast.mapreduce.Reducer;
import com.hazelcast.mapreduce.ReducerFactory;
public class MinMaxReducerFactory implements ReducerFactory<String, Double, Double> {
#Override
public Reducer<Double, Double> newReducer(String key) {
return new MinMaxReducer(MinMaxMapper.MAX.equals(key) ? true : false);
}
private static class MinMaxReducer extends Reducer<Double, Double> {
private final boolean maxReducer;
private volatile double value;
private MinMaxReducer(boolean maxReducer) {
this.maxReducer = maxReducer;
this.value = maxReducer ? -Double.MAX_VALUE : Double.MAX_VALUE;
}
#Override
public void reduce(Double value) {
if (maxReducer) {
this.value = Math.max(value, this.value);
} else {
this.value = Math.min(value, this.value);
}
}
#Override
public Double finalizeReduce() {
return value;
}
}
}
Returns two elements map with min and max:
ICompletableFuture<Map<String, Double>> future =
job.mapper(new MinMaxMapper())
.combiner(new MinMaxCombinerFactory())
.reducer(new MinMaxReducerFactory())
.submit();
Map<String, Double> result = future.get();
Why don't you create an ordered index? Although I'm not quite sure if it currently is possible to find a maximum value using a predicate and once found, abort the evaluation of the predicate.
I spent some time to try to make a collection that:
1) is sorted by value (not by key)
2) is sorted each time an element is added or modified
3) is fixed size and discard automatically smallest/biggest element depending of the sort way
4) is safe thread
So 3) and 4) I think it is quite ok. For 1) and 2) it was a bit more tricky. I spent quite a long time on this thread, experimenting the different sample, but one big issue is that the collection are sorted only once when object are inserted.
Anyway, I try to implement my own collection, which is working (shouldn't be used for huge data as it is sorted quite often) but I'm not so happy with the design. Especially in the fact that my value objects are constrained to be Observable (which is good) but not comparable so I had to use a dirty instanceof + exception for this.
Any sugestion to improve this ?
Here is the code:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
public class SortedDiscardingSyncArray<K, V extends Observable> implements Observer {
// Comparison way (ascendent or descendant)
public static enum ComparisonWay
{
DESC,
ASC;
}
// this is backed by a List (and ArrayList impl)
private List<ArrayElement> array;
// Capacity, configurable, over this limit, an item will be discarded
private int MAX_CAPACITY = 200;
// default is descending comparison
private ComparisonWay compareWay = ComparisonWay.DESC;
public SortedDiscardingSyncArray(ComparisonWay compareWay, int mAX_CAPACITY) {
super();
this.compareWay = compareWay;
MAX_CAPACITY = mAX_CAPACITY;
array = new ArrayList <ArrayElement>(MAX_CAPACITY);
}
public SortedDiscardingSyncArray(int mAX_CAPACITY) {
super();
MAX_CAPACITY = mAX_CAPACITY;
array = new ArrayList<ArrayElement>(MAX_CAPACITY);
}
public SortedDiscardingSyncArray() {
super();
array = new ArrayList <ArrayElement>(MAX_CAPACITY);
}
public boolean put(K key, V value)
{
try {
return put (new ArrayElement(key, value, this));
} catch (Exception e) {
e.printStackTrace();
return false;
}
finally
{
sortArray();
}
}
private synchronized boolean put(ArrayElement ae)
{
if (array.size() < MAX_CAPACITY)
{
return array.add(ae);
}
// check if last one is greater/smaller than current value to insert
else if (ae.compareTo(array.get(MAX_CAPACITY-1)) < 0)
{
array.remove(MAX_CAPACITY - 1);
return array.add(ae);
}
// else we don't insert
return false;
}
public V getValue (int index)
{
return array.get(index).getValue();
}
public V getValue (K key)
{
for (ArrayElement ae : array)
{
if (ae.getKey().equals(key)) return ae.getValue();
}
return null;
}
public K getKey (int index)
{
return array.get(index).getKey();
}
private void sortArray()
{
Collections.sort(array);
}
public synchronized void setValue(K key, V newValue) {
for (ArrayElement ae : array)
{
if (ae.getKey().equals(key))
{
ae.setValue(newValue);
return;
}
}
}
public int size() {
return array.size();
}
#Override
public void update(java.util.Observable arg0, Object arg1) {
sortArray();
}
public static void main(String[] args) {
// some test on the class
SortedDiscardingSyncArray<String, ObservableSample> myData = new SortedDiscardingSyncArray<String, ObservableSample>(ComparisonWay.DESC, 20);
String Ka = "Ka";
String Kb = "Kb";
String Kc = "Kc";
String Kd = "Kd";
myData.put(Ka, new ObservableSample(0));
myData.put(Kb, new ObservableSample(3));
myData.put(Kc, new ObservableSample(1));
myData.put(Kd, new ObservableSample(2));
for (int i=0; i < myData.size(); i++)
{
System.out.println(myData.getKey(i).toString() + " - " + myData.getValue(i).toString());
}
System.out.println("Modifying data...");
myData.getValue(Kb).setValue(12);
myData.getValue(Ka).setValue(34);
myData.getValue(Kd).setValue(9);
myData.getValue(Kc).setValue(19);
for (int i=0; i < myData.size(); i++)
{
System.out.println(myData.getKey(i).toString() + " - " + myData.getValue(i).toString());
}
}
private class ArrayElement implements Comparable <ArrayElement> {
public ArrayElement(K key, V value, Observer obs) throws Exception {
super();
// don't know how to handle that case
// maybe multiple inheritance would have helped here ?
if (! (value instanceof Comparable)) throw new Exception("Object must be 'Comparable'");
this.key = key;
this.value = value;
value.addObserver(obs);
}
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append(key);
sb.append(" - ");
sb.append(value);
return sb.toString();
}
private K key;
private V value;
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public synchronized void setValue(V value) {
this.value = value;
}
#SuppressWarnings("unchecked")
#Override
public int compareTo(ArrayElement o) {
int c;
if (compareWay == ComparisonWay.DESC) c = ((Comparable<V>) o.getValue()).compareTo(this.getValue());
else c = ((Comparable<V>) this.getValue()).compareTo(o.getValue());
if (c != 0) {
return c;
}
Integer hashCode1 = o.getValue().hashCode();
Integer hashCode2 = this.getValue().hashCode();
// we don't check the compare way for hash code (useless ?)
return hashCode1.compareTo(hashCode2);
}
}
}
And the other class for testing purpose:
import java.util.Observable;
public class ObservableSample extends Observable implements Comparable <ObservableSample>
{
private Integer value = 0;
public ObservableSample(int value) {
this.value = value;
setChanged();
notifyObservers();
}
public String toString()
{
return String.valueOf(this.value);
}
public void setValue(Integer value) {
this.value = value;
setChanged();
notifyObservers();
}
public Integer getValue() {
return value;
}
#Override
public int compareTo(ObservableSample o) {
int c;
c = (this.getValue()).compareTo(o.getValue());
if (c != 0) {
return c;
}
Integer hashCode1 = o.getValue().hashCode();
Integer hashCode2 = this.getValue().hashCode();
// we don't check the compare way for hash code (useless ?)
return hashCode1.compareTo(hashCode2);
}
}
Collections are difficult to write, maybe you should look for an existing implementation.
Try checking out ImmutableSortedSet from Guava.
You can have a marker interface
public interface ComparableObservable extends Observable, Comparable {
}
and then change
SortedDiscardingSyncArray<K, V extends Observable>
to
SortedDiscardingSyncArray<K, V extends ComparableObservable>
to avoid the explicit cast.
Other than that the code is quite verbose and I didn't follow it completely. I would also suggest having a look at guava or (apache) commons-collections library to explore if you can find something reusable.
You can write generic wildcards with multiple bounds. So change your declaration of <K, V extends Observable> to <K, V extends Observable & Comparable<V>> and then you can treat V as if it implements both interfaces, without an otherwise empty and useless interface.
Another few things: Pick a naming convention, and stick with it. The one I use is that a name such as MAX_CAPACITY would be used for a static final field (i.e. a constant, such as a default) and that the equivalent instance field would be maxCapacity Names such as mAX_CAPACITY would be right out of the question.
See: Oracle's naming conventions for Java
Instead of using a ComparisonWay enum, I would take a custom Comparator. Much more flexible, and doesn't replicate something that already exists.
See: the Comparator API docs
Your code, as written, is not thread safe. In particular an observed element calling the unsynchronized update method may thus invoke sortArray without obtaining the proper lock. FindBugs is a great tool that catches a lot of problems like this.
Your ObservableSample does not really follow good practices with regards to how it implements Comparable, in that it does not really compare data values but instead the hashCode. The hashCode is essentially arbitrary and collisions are quite possible. Additionally, the Comparable interface requests that usually you should be "consistent with Equals", for which you also might want to take a look at the documentation for the Object class's equals method
Yes, it sounds like a lot of work, but if you go through it and do it right you will save yourself astounding amounts of debugging effort down the road. If you do not do these properly and to the spec, you will find that when you place it in Sets or Maps your keys or values strangely disappear, reappear, or get clobbered. And it will depend on which version of Java you run, potentially!
Here is a version updated. Still not completly sure it is safe thread but findbugs tool didn't give so usefull tips. Also for the comparisonWay, I don't want to constraint the user to develop its own comparator, I want to keep the things simple.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
public class SortedDiscardingSyncArray<K, V extends Observable & Comparable<V>> implements Observer {
// Comparison way (ascendent or descendant)
public static enum ComparisonWay { DESC, ASC; }
// this is backed by a List (and ArrayList)
private List<ArrayElement> array;
// Capacity, configurable, over this limit, an item will be discarded
private int maxCapacity = 200;
// default is descending comparison
private ComparisonWay compareWay = ComparisonWay.DESC;
public SortedDiscardingSyncArray(ComparisonWay compareWay, int maxCapacity) {
super();
this.compareWay = compareWay;
this.maxCapacity = maxCapacity;
array = new ArrayList <ArrayElement>(maxCapacity);
}
public SortedDiscardingSyncArray(int maxCapacity) {
super();
this.maxCapacity = maxCapacity;
array = new ArrayList<ArrayElement>(maxCapacity);
}
public SortedDiscardingSyncArray() {
super();
array = new ArrayList <ArrayElement>(maxCapacity);
}
// not synchronized, but calling internal sync put command
public boolean put(K key, V value)
{
try {
return put (new ArrayElement(key, value, this));
} catch (Exception e) {
e.printStackTrace();
return false;
}
finally
{
sortArray();
}
}
private synchronized boolean put(ArrayElement ae)
{
if (array.size() < maxCapacity) return array.add(ae);
// check if last one is greater/smaller than current value to insert
else if (ae.compareTo(array.get(maxCapacity-1)) < 0)
{
array.remove(maxCapacity - 1);
return array.add(ae);
}
// else we don't insert and return false
return false;
}
public V getValue (int index)
{
return array.get(index).getValue();
}
public V getValue (K key)
{
for (ArrayElement ae : array)
{
if (ae.getKey().equals(key)) return ae.getValue();
}
return null;
}
public K getKey (int index)
{
return array.get(index).getKey();
}
private synchronized void sortArray()
{
Collections.sort(array);
}
public synchronized void setValue(K key, V newValue) {
for (ArrayElement ae : array)
{
if (ae.getKey().equals(key))
{
ae.setValue(newValue);
return;
}
}
}
public int size() {
return array.size();
}
#Override
public void update(java.util.Observable arg0, Object arg1) {
sortArray();
}
public static void main(String[] args) {
// some test on the class
SortedDiscardingSyncArray<String, ObservableSample> myData = new SortedDiscardingSyncArray<String, ObservableSample>(ComparisonWay.DESC, 20);
String Ka = "Ka";
String Kb = "Kb";
String Kc = "Kc";
String Kd = "Kd";
myData.put(Ka, new ObservableSample(0));
myData.put(Kb, new ObservableSample(3));
myData.put(Kc, new ObservableSample(1));
myData.put(Kd, new ObservableSample(2));
for (int i=0; i < myData.size(); i++)
{
System.out.println(myData.getKey(i).toString() + " - " + myData.getValue(i).toString());
}
System.out.println("Modifying data...");
myData.getValue(Kb).setValue(12);
myData.getValue(Ka).setValue(34);
myData.getValue(Kd).setValue(9);
myData.getValue(Kc).setValue(19);
for (int i=0; i < myData.size(); i++)
{
System.out.println(myData.getKey(i).toString() + " - " + myData.getValue(i).toString());
}
}
private class ArrayElement implements Comparable <ArrayElement> {
public ArrayElement(K key, V value, Observer obs) throws Exception {
super();
this.key = key;
this.value = value;
value.addObserver(obs);
}
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append(key);
sb.append(" - ");
sb.append(value);
return sb.toString();
}
private K key;
private V value;
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public synchronized void setValue(V value) {
this.value = value;
}
#Override
public int compareTo(ArrayElement o) {
int c;
if (compareWay == ComparisonWay.DESC) c = o.getValue().compareTo(this.getValue());
else c = this.getValue().compareTo(o.getValue());
if (c != 0) {
return c;
}
Integer hashCode1 = o.getValue().hashCode();
Integer hashCode2 = this.getValue().hashCode();
// we don't check the compare way for hash code (useless ?)
return hashCode1.compareTo(hashCode2);
}
}
}