I have Caffeine cache with Key->Value mapping. There are multiple implementations of Key interface with different equals methods. In order to delete value from cache based on someOtherVal, I had to use code like cache.asMap().keySet().removeIf(comp::isSame) which is super slow.
Is there any other solution for this kind of many keys to single value mapping in cache? One thing that comes to my mind is to have 2 Cache instances, one with Cache<Key, String> and other with Cache<someOtherVal, Key>, and whenever I want to delete a value I locate Key using this other cache.
Then only question is how to keep this 2 caches in sync? Are there already solutions for this?
import java.time.Duration;
import java.util.Objects;
import java.util.UUID;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Stopwatch;
public class Removal {
private static final int MAX = 1_000_000;
interface Key{
String getSomeOtherVal();
default boolean isSame(Key k){
return Objects.equals(k.getSomeOtherVal(),getSomeOtherVal());
}
}
static class KeyImpl implements Key{
int id;
String someOtherVal;
public KeyImpl(int id, String someOtherVal) {
this.id = id;
this.someOtherVal = someOtherVal;
}
public int getId() {
return id;
}
#Override
public String getSomeOtherVal() {
return someOtherVal;
}
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
KeyImpl key = (KeyImpl)o;
return id == key.id;
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
Cache<Key, String> cache = Caffeine.newBuilder().build();
public static void main(String[] args) {
Removal s = new Removal();
s.fill();
Duration sRem = s.slowRemovalFirst100();
Duration fRem = s.fastRemoval100To200();
System.out.println("Slow removal in " + sRem);
System.out.println("Fast removal in " + fRem);
}
private Duration slowRemovalFirst100(){
Stopwatch sw = Stopwatch.createStarted();
for(int i=0; i<100; i++){
Key comp = new KeyImpl(i, String.valueOf(i));
cache.asMap().keySet().removeIf(comp::isSame); //Finds a key by some other property and then removes it (SLOW)
//System.out.println("Removed " + i);
}
return sw.stop().elapsed();
}
private Duration fastRemoval100To200(){
Stopwatch sw = Stopwatch.createStarted();
for(int i=100; i<200; i++){
Key comp = new KeyImpl(i, String.valueOf(i));
cache.invalidate(comp); //Uses direct access to map by key (FAST)
//System.out.println("Removed " + i);
}
return sw.stop().elapsed();
}
private void fill(){
for(int i=0; i<MAX; i++){
cache.put(new KeyImpl(i, String.valueOf(i)), UUID.randomUUID().toString());
}
}
}
Result of running this code on my machine:
Slow removal in PT2.807105177S
Fast removal in PT0.000126183S
where you can see such a big difference...
Ok, I managed to solve this:
public class IndexedCache<K,V> implements Cache<K,V> {
#Delegate
private Cache<K, V> cache;
private Map<Class<?>, Map<Object, Set<K>>> indexes;
private IndexedCache(Builder<K, V> bldr){
this.indexes = bldr.indexes;
cache = bldr.caf.build();
}
public <R> void invalidateAllWithIndex(Class<R> clazz, R value) {
cache.invalidateAll(indexes.get(clazz).getOrDefault(value, new HashSet<>()));
}
public static class Builder<K, V>{
Map<Class<?>, Function<K, ?>> functions = new HashMap<>();
Map<Class<?>, Map<Object, Set<K>>> indexes = new ConcurrentHashMap<>();
Caffeine<K,V> caf;
public <R> Builder<K,V> withIndex(Class<R> clazz, Function<K, R> function){
functions.put(clazz, function);
indexes.put(clazz, new ConcurrentHashMap<>());
return this;
}
public IndexedCache<K, V> buildFromCaffeine(Caffeine<Object, Object> caffeine) {
caf = caffeine.writer(new CacheWriter<K, V>() {
#Override
public void write( K k, V v) {
for(Map.Entry<Class<?>, Map<Object, Set<K>>> indexesEntry : indexes.entrySet()){
indexesEntry.getValue().computeIfAbsent(functions.get(indexesEntry.getKey()).apply(k), (ky)-> new HashSet<>())
.add(k);
}
}
#Override
public void delete( K k, V v, RemovalCause removalCause) {
for(Map.Entry<Class<?>, Map<Object, Set<K>>> indexesEntry : indexes.entrySet()){
indexesEntry.getValue().remove(functions.get(indexesEntry.getKey()).apply(k));
}
}
});
return new IndexedCache<>(this);
}
}
}
and this is use-case:
#AllArgsConstructor
#Data
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
static class CompositeKey{
#EqualsAndHashCode.Include
Integer k1;
String k2;
Long k3;
}
public static void main(String[] args) {
Caffeine<Object, Object> cfein = Caffeine.newBuilder().softValues().maximumSize(200_000);
IndexedCache<CompositeKey, String> cache = new IndexedCache.Builder<CompositeKey, String>()
.withIndex(Long.class, ck -> ck.getK3())
.withIndex(String.class, ck -> ck.getK2())
.buildFromCaffeine(cfein);
for(int i=0; i<100; i++){
cache.put(new CompositeKey(i, String.valueOf(i), Long.valueOf(i)), "sdfsdf");
}
for(int i=0; i<10; i++){
//use equals method of CompositeKey to do equals comp.
cache.invalidate(new CompositeKey(i, String.valueOf(i), Long.valueOf(i)));
}
for(int i=10; i<20; i++){
//use Long index
cache.invalidateAllWithIndex(Long.class, Long.valueOf(i));
}
for(int i=20; i<30; i++){
//use String index
cache.invalidateAllWithIndex(String.class, String.valueOf(i));
}
int y = 4;
}
here is link to discussion I had: https://github.com/ben-manes/caffeine/issues/279
Related
I'm implementing a TreeMap in java today to track the navigation for next page.
I have 5 entries in the treemap, 3 of them works while 2 don't work.
NavigationHelper.java :
public class NavigationHelper {
private static String hhSection = HXConstants.CSR_SECTION_FAMILY_DETAILS;
private static String[] hhPages = {
HXConstants.CSR_PAGE_ID_HOUSEHOLD_MEMBERS,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_RELATIONSHIP,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_ADDITIONAL_QUESTIONS,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_SUMMARY_NEW,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_PRIVACY_AGREEMENT
};
private static String hhPath = "household";
public static HashMap<String, NavLocation> getHHNavMap()
{
HashMap<String, NavLocation> hhNavMap = new HashMap<String, NavLocation>();
for (int i=0;i<hhPages.length;i++ ) {
hhNavMap.put(hhPath+"/"+hhPages[i], new NavLocation(hhSection,hhPages[i]));
}
return hhNavMap;
}
public static Map<NavLocation,String> getHHBackNavMap() {
TreeMap<NavLocation,String> hhBackNavMap = new TreeMap<NavLocation, String>();
HashMap<String, NavLocation> hhNavMap = getHHNavMap();
for(Entry<String, NavLocation> entry : hhNavMap.entrySet()) {
hhBackNavMap.put(entry.getValue(), entry.getKey());
}
return hhBackNavMap;
}
public static class NavLocation implements Comparable<NavLocation>{
private String section;
public NavLocation(String s, String p) {
this.section = s;
this.page = p;
}
public String getSection() {
return section;
}
public String getPage() {
return page;
}
private String page;
#Override
public int compareTo(NavLocation navObj) {
if(navObj.getPage().equals(this.page) && (navObj.getSection().equals(this.section)))
return 0;
return 1;
}
}
}
AppAggNavigationHelper.java :
public class AppAggNavigationHelper extends RestServiceBaseTest {
private static String hhSection = HXConstants.CSR_SECTION_FAMILY_DETAILS;
private static String[] hhPages = {
HXConstants.CSR_PAGE_ID_HOUSEHOLD_MEMBERS,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_RELATIONSHIP,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_ADDITIONAL_QUESTIONS,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_SUMMARY_NEW,
HXConstants.CSR_PAGE_ID_HOUSEHOLD_PRIVACY_AGREEMENT
};
NavigationHelper navigationHelper = new NavigationHelper();
List<NavLocation> navList = new ArrayList<NavLocation>();
#Before
public void populateNavLocations() {
for(int i = 0 ; i < hhPages.length ; i++) {
navList.add(new NavLocation(hhSection, hhPages[i]));
}
}
#Test
public void test() {
testWithoutRest();
}
public void testWithoutRest() {
TreeMap<NavLocation,String> map = (TreeMap<NavLocation, String>) navigationHelper.getHHBackNavMap();
for(Map.Entry<NavLocation, String> entry : map.entrySet()) {
NavLocation nav = entry.getKey();
System.out.println(nav.getPage() + " " + nav.getSection());
System.out.println(entry.getValue());
}
p("*****");
for(NavLocation navLocation : navList) {
System.out.println(navLocation.getPage() + " " + navLocation.getSection() + " " + map.get(navLocation));
}
}
}
Then the output is wired, for member, summary, privacy it's working.
But for relation and questions it's not.
:
member familydetails household/member
relation familydetails null
question familydetails null
summary familydetails household/summary
privacy familydetails household/privacy
Why relation and question are not working?
Your compareTo method is broken. If two NavLocation objects, a and b differ in their page or section, both a.compareTo(b) and b.compareTo(a) will return 1, thus violating the general contract of the method, which may lead to unexpected results.
Instead, the classic way to implement such a method depending on the objects properties would probably look something like this:
#Override
public int compareTo(NavLocation other) {
int result = getPage().compareTo(other.getPage());
if (result != 0) {
return result;
}
return getSection().compareTo(other.getSection());
}
I am trying to sort my table by the sales figures.
For example:
Names Figures
A 400
B 200
C 500
will be
Names Figures
C 500
A 400
B 200
after being sorted. This is my code. I am a beginner pardon my code hehe
Scanner input = new Scanner(System.in);
int numAsso;
System.out.print("Enter the Number of Associates: ");
numAsso = input.nextInt();
String[] names = new String[numAsso];
String line;
for (int i = 0; i < numAsso; i++)
{
System.out.print("Enter the name of the Associate: ");
names[i] = input.next();
}
final Double[][] sales = new Double [numAsso][2];
double sum= 0;
for(int j = 0; j < numAsso; j++)
{
System.out.print("Enter Total Figures for "+ names[j]+": ");
sales[j][0] = input.nextDouble();
sum+=sales[j][0];
}
double average = sum/numAsso;
System.out.println("Names\t\t"+"Figures\t\t"+"Average");
for(int x = 0; x < numAsso; x++)
{
System.out.println(names[x] + "\t\t" + sales[x][0] + "\t\t" + average);
}
You should store your data in a Map and sort it by values, you can take a look to these links:
Java – Sort Map By Value
Sort a Map<Key, Value> by values (Java)
Utility Class approach
Instead of a static sorter method you can use this utility Class to sort entries as they are added to the map:
public class ValueSortedMap<K extends Comparable<K> ,V extends Comparable<V> > extends TreeMap<K,V> {
private TreeMap<K,V> sortedMap;
private ValueComparator comparator;
private boolean reverseOrder = false;
public ValueSortedMap() {
this.comparator = new ValueComparator();
this.sortedMap = new TreeMap<K, V>(comparator);
}
public ValueSortedMap(boolean reverseOrder) {
this();
this.reverseOrder = reverseOrder;
}
#Override
public String toString() {
return sortedMap.toString();
}
#Override
public V put(K key, V value) {
if(sortedMap.containsKey(key)){
//remove the key in the sorted set before adding the key again
remove(key);
}
comparator.unsortedMap.put(key, value);
return sortedMap.put(key, value);
}
#Override
public Map.Entry<K, V> firstEntry() {
return sortedMap.firstEntry();
}
#Override
public void clear() {
sortedMap.clear();
comparator.unsortedMap.clear();
}
#Override
public boolean containsKey(Object key) {
return sortedMap.containsKey(key);
}
#Override
public boolean containsValue(Object value) {
return sortedMap.containsValue(value);
}
#Override
public Set<Map.Entry<K, V>> entrySet() {
return sortedMap.entrySet();
}
#Override
public V get(Object key) {
return sortedMap.get(key);
}
/**
* 0-based position
* #param position
* #return
*/
public Map.Entry<K, V> getEntryInPosition(int position) {
Iterator<Map.Entry<K,V>> iterator = sortedMap.entrySet().iterator();
int i = 0;
while(iterator.hasNext()){
if(i == position){
return iterator.next();
}else{
i++;
}
}
return null;
}
#Override
public boolean isEmpty() {
return sortedMap.isEmpty();
}
#Override
public Set<K> keySet() {
return sortedMap.keySet();
}
#Override
public void putAll(Map<? extends K, ? extends V> map) {
for(Map.Entry<? extends K, ? extends V> entry : map.entrySet()){
put(entry.getKey(), entry.getValue());
}
}
#Override
public int size() {
return sortedMap.size();
}
#Override
public Collection<V> values() {
return sortedMap.values();
}
#Override
public V remove(Object key) {
sortedMap.remove(key);
return comparator.unsortedMap.remove(key);
}
public class ValueComparator implements Comparator<K>{
public Map<K,V> unsortedMap = new HashMap<K,V>();
#Override
public int compare(K k1, K k2) {
Comparable<V> v1 = unsortedMap.get(k1);
Comparable<V> v2 = unsortedMap.get(k2);
if(Objects.equal(v1,v2)){
return k1.compareTo(k2); //not using reverseOrder comparing keys
}else{
if(reverseOrder){
return ComparisonChain.start().compare(v2, v1, Ordering.natural().nullsFirst()).result();
}else{
return ComparisonChain.start().compare(v1, v2, Ordering.natural().nullsFirst()).result();
}
}
}
}
}
If you would like to stick to using an array, you can sort the array storing the sales figures normally, but make the same changes to the names array.
For example, swapping the positions of 200 and 400 in the sort would be accompanied by swapping the positions of B and A in the other dimension of the array.
As others mentioned, using a map is probably the best way to go about this, but this also works if you would like to keep it a little simpler.
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 List of Pin objects (List<Pin>) where the Pin class has the following attributes:
String pinNumber, String pinType, Date insertDate
I would like to get a HashMap with <String pinNumber, int count> that have the distinct pinNumber telling me how many distinct pinNumber are in the List<Pin> and a count of each.
So the way I know of to do this is to:
Iterate through the List<Pin>
Check if the HashMap contains already the key value of the pinNumber and:
Increase it or add it if it does not exist.
I would like to do the same for each of the fields from the Pin object.
I am sure there should be an easier way to do this?
Maybe Guava has something simpler?
If you have the possibility to use Java 8 (and since what you want to do basically sounds like a "group by" operation), this can be solved in an elegant way using the new Stream API (as hinted by user vallismortis):
import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) {
List<Pin> pins = Arrays.asList(
new Pin("PIN-1", "T1", new Date()),
new Pin("PIN-1", "T2", new Date()),
new Pin("PIN-1", "T3", new Date()),
new Pin("PIN-2", "T2", new Date()),
new Pin("PIN-2", "T2", new Date()),
new Pin("PIN-3", "T2", new Date())
);
Map<String, Long> map = pins.stream().collect(groupingBy(Pin::getPinNumber, counting()));
System.out.println("map = " + map);
}
}
class Pin {
String pinNumber;
String pinType;
Date insertDate;
public Pin(String pinNumber, String pinType, Date insertDate) {
this.pinNumber = pinNumber;
this.pinType = pinType;
this.insertDate = insertDate;
}
public String getPinNumber() {
return pinNumber;
}
public String getPinType() {
return pinType;
}
public Date getInsertDate() {
return insertDate;
}
}
Output:
map = {PIN-1=3, PIN-3=1, PIN-2=2}
You don't need Guava for this. You can use standard Java 8 features. One way is with streams, however they aren't a good fit if you need to compute counts for more than one field. Instead, you could use the Map.merge method:
Map<String, Integer> byNumber = new HashMap<>();
Map<String, Integer> byType = new HashMap<>();
Map<Date, Integer> byInsertDate = new HashMap<>();
listOfPins.forEach(pin -> {
byNumber.merge(pin.getPinNumber(), 1, Integer::sum);
byType.merge(pin.getPinType(), 1, Integer::sum);
byInsertDate.merge(pin.getInsertDate(), 1, Integer::sum);
});
This has the advantage that it can be accomplished in only one iteration over listOfPins, while with streams, you'd need one pass for each field.
Here is one possible solution if you didn't want to rely on another library and wanted to maintain backward compatibility with older JVMs. It isn't the best, or the easiest to use, but it does work.
FrequencyUtil.java
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class FrequencyUtil
{
private static FrequencyUtil SINGLETON;
private static FrequencyUtil getInstance()
{
if (FrequencyUtil.SINGLETON == null)
{
FrequencyUtil.SINGLETON = new FrequencyUtil();
}
return FrequencyUtil.SINGLETON;
}
public static <X> Map<X, Integer> frequency(final Collection<X> objects, final Comparator<X> comparator)
{
Map<ComparatorWrapper<X>, Integer> frequencies = new HashMap<ComparatorWrapper<X>, Integer>();
for (X object : objects)
{
ComparatorWrapper<X> wrapper = FrequencyUtil.getInstance().new ComparatorWrapper<X>(object, comparator);
Integer count = frequencies.get(wrapper);
frequencies.put(wrapper, (count == null) ? 1 : count + 1);
}
// unwrap the frequencies
Map<X, Integer> frequenciesRaw = new HashMap<X, Integer>();
for (ComparatorWrapper<X> wrapper : frequencies.keySet())
{
frequenciesRaw.put(wrapper.getObject(), frequencies.get(wrapper));
}
return frequenciesRaw;
}
private class ComparatorWrapper<Z>
{
private Z object;
private Comparator<Z> comparator;
ComparatorWrapper(final Z object, final Comparator<Z> comparator)
{
this.object = object;
this.comparator = comparator;
return;
}
public Z getObject()
{
return this.object;
}
#Override
public int hashCode()
{
return 0;
}
#SuppressWarnings("unchecked")
#Override
public boolean equals(Object obj)
{
if ((obj == null) || !(obj instanceof ComparatorWrapper))
{
return false;
}
return this.comparator.compare(this.object, ((ComparatorWrapper<Z>) obj).getObject()) == 0;
}
}
}
FrequencyTest.java
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class FrequencyTest
{
public void test()
{
List<Pin> pins = new ArrayList<Pin>();
Pin pin1 = new Pin();
Pin pin2 = new Pin();
Pin pin3 = new Pin();
pin1.setPinType("typeA");
pin2.setPinType("typeB");
pin3.setPinType("typeA");
pin1.setPinNumber("50");
pin2.setPinNumber("50");
pin3.setPinNumber("80");
pin1.setInsertDate(Calendar.getInstance().getTime());
pin2.setInsertDate(Calendar.getInstance().getTime());
pin3.setInsertDate(Calendar.getInstance().getTime());
pins.add(pin1);
pins.add(pin2);
pins.add(pin3);
Comparator<Pin> pinTypeComparator = new Comparator<Pin>()
{
#Override
public int compare(final Pin o1, final Pin o2)
{
return o1.getPinType().compareTo(o2.getPinType());
}
};
Comparator<Pin> pinNumberComparator = new Comparator<Pin>()
{
#Override
public int compare(final Pin o1, final Pin o2)
{
return o1.getPinNumber().compareTo(o2.getPinNumber());
}
};
Comparator<Pin> insertDateComparator = new Comparator<Pin>()
{
private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
#Override
public int compare(final Pin o1, final Pin o2)
{
return this.sdf.format(o1.getInsertDate()).compareTo(this.sdf.format(o2.getInsertDate()));
}
};
Map<Pin, Integer> pinTypeFrequency = FrequencyUtil.frequency(pins, pinTypeComparator);
Map<Pin, Integer> pinNumberFrequency = FrequencyUtil.frequency(pins, pinNumberComparator);
Map<Pin, Integer> insertDateFrequency = FrequencyUtil.frequency(pins, insertDateComparator);
System.out.println("pinTypeFrequency");
for (Pin pin : pinTypeFrequency.keySet())
{
System.out.println(pin.getPinType() + ": " + pinTypeFrequency.get(pin));
}
System.out.println();
System.out.println("pinNumberFrequency");
for (Pin pin : pinNumberFrequency.keySet())
{
System.out.println(pin.getPinNumber() + ": " + pinNumberFrequency.get(pin));
}
System.out.println();
System.out.println("insertDateFrequency");
for (Pin pin : insertDateFrequency.keySet())
{
System.out.println(pin.getInsertDate().toString() + ": " + insertDateFrequency.get(pin));
}
}
public static void main(String[] args)
{
try
{
new FrequencyTest().test();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
System.exit(0);
}
}
Pin.java
import java.util.Date;
public class Pin
{
private String pinNumber;
private String pinType;
private Date insertDate;
public String getPinNumber()
{
return pinNumber;
}
public void setPinNumber(String pinNumber)
{
this.pinNumber = pinNumber;
}
public String getPinType()
{
return pinType;
}
public void setPinType(String pinType)
{
this.pinType = pinType;
}
public Date getInsertDate()
{
return insertDate;
}
public void setInsertDate(Date insertDate)
{
this.insertDate = insertDate;
}
}
output
pinTypeFrequency typeB: 1 typeA: 2
pinNumberFrequency 80: 1 50: 2
insertDateFrequency Mon Jun 22 12:09:19 EDT 2015: 3
Just for fun and historical reference, a Java 1.2 version:
FrequencyUtil12.java
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class FrequencyUtil
{
private static FrequencyUtil SINGLETON;
private static FrequencyUtil getInstance()
{
if (FrequencyUtil.SINGLETON == null)
{
FrequencyUtil.SINGLETON = new FrequencyUtil();
}
return FrequencyUtil.SINGLETON;
}
public static Map frequency(final Collection objects, final Comparator comparator)
{
Map frequencies = new HashMap();
Iterator iter = objects.iterator();
while (iter.hasNext())
{
Object object = iter.next();
ComparatorWrapper wrapper = FrequencyUtil.getInstance().new ComparatorWrapper(object, comparator);
Integer count = (Integer) frequencies.get(wrapper);
frequencies.put(wrapper, (count == null) ? 1 : count + 1);
}
// unwrap the frequencies
Map frequenciesRaw = new HashMap();
Iterator keys = frequencies.keySet().iterator();
while (keys.hasNext())
{
ComparatorWrapper wrapper = (ComparatorWrapper) keys.next();
frequenciesRaw.put(wrapper.getObject(), frequencies.get(wrapper));
}
return frequenciesRaw;
}
private class ComparatorWrapper
{
private Object object;
private Comparator comparator;
ComparatorWrapper(final Object object, final Comparator comparator)
{
this.object = object;
this.comparator = comparator;
return;
}
public Object getObject()
{
return this.object;
}
public int hashCode()
{
return 0;
}
public boolean equals(Object obj)
{
if ((obj == null) || !(obj instanceof ComparatorWrapper))
{
return false;
}
return this.comparator.compare(this.object, ((ComparatorWrapper) obj).getObject()) == 0;
}
}
}
Even Simpler implementation:
public static void main(String[] args) {
List<Pin> pinList = new ArrayList<Pin>();
// Add employee to list
pinList.add(new Pin("1234", "local", null));
pinList.add(new Pin("2345", "extra", null));
pinList.add(new Pin("3456", "extra", null));
pinList.add(new Pin("1234", "local", null));
Map<String, Integer> mapPinNumber = new HashMap<String, Integer>();
for (Pin pin : pinList) {
Integer cnt = mapPinNumber.get(pin.getPinNumber());
mapPinNumber.put(pin.getPinNumber(), (cnt == null) ? 1 : ++cnt);
}
printMap(mapPinNumber);
Map<String, Integer> mapPinType = new HashMap<String, Integer>();
for (Pin pin : pinList) {
Integer cnt = mapPinType.get(pin.getPinType());
mapPinType.put(pin.getPinType(), (cnt == null) ? 1 : ++cnt);
}
printMap(mapPinType);
}
private static void printMap(Map<String, Integer> map) {
String key;
int value;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
key = entry.getKey();
value = entry.getValue();
System.out.println(key + ": " + value);
}
}
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);
}
}
}