I use the code below to sort my hashmap by its value. But the result seems confused because it only keep one entry for one value and remove another entry with duplicate value.
Here is the Comparator code:
class ValueComparator implements Comparator {
Map map;
public ValueComparator(Map map) {
this.map = map;
}
public int compare(Object keyA, Object keyB) {
Comparable valueA = (Comparable) map.get(keyA);
Comparable valueB = (Comparable) map.get(keyB);
return valueB.compareTo(valueA);
}
And here is how I use it:
TreeMap sortedMap=new TreeMap(new ValueComparator(allCandidateMap));
sortedMap.putAll(allCandidateMap);
That makes perfect sense. You've declared that if two keys map to equal values in allCandidateMap, they should be considered equal, since your compare will be returning 0.
What this comes down to is that your comparator has almost reversed the roles of key and value. If you try doing other operations you will probably find that the values of the map often behave like keys. Methods like get and containsKey will act as if they're looking up the values, not the keys (but then get will return the value you passed in, so values are still values as well). The comparator defines the behaviour of the TreeMap, and you've asked for very weird behaviour.
Related
I have written a method that sorts the TreeMap by its values
public TreeMap<String, Integer> sortByValues(final TreeMap<String, Integer> map) {
Comparator<String> valueComparator = new Comparator<String>() {
public int compare(String k1, String k2) {
int compare = map.get(k1).compareTo(map.get(k2));
return compare;
}
};
Map<String, Integer> sortedByValues = new TreeMap<String, Integer>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
The above method works fine in normal case but fails when there is a duplicate value present in the TreeMap. Any duplicate value entry is removed from the Map.After googling it I found the solution as
public Map<String, Integer> sortByValuesTree(final Map<String, Integer> map) {
Comparator<String> valueComparator = new Comparator<String>() {
public int compare(String k1, String k2) {
int compare = map.get(k1).compareTo(map.get(k2));
if (compare == 0) return 1;
else return compare;
}
};
Map<String, Integer> sortedByValues = new TreeMap<String, Integer>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
The above works fine but I am not able to understand why first method didn't work. Why did it remove duplicate value entry? Can someone please let me know
Why did it remove duplicate value entry?
Because that's the very definition of a Map: for a given key, it stores one value. If you put another value for a key that is already in the map, the new value replaces the old one for this key. And since you told the map that a key is equal to another one when their associated value are equal, the map considers two keys to be equal when their value are equal.
Note that your solution is a bad one which probably works by accident. Indeed, your comparator doesn't respect the contract of the Comparator interface. Indeed, when two values are equal, you arbitrarily decide to make the first one bigger than the second one. This means that your comparator makes A > B and B > A true at the same time, which is not correct.
Sorting a TreeMap by value just looks like an absurdity to me. You won't be able to add any new value to the map anyway, since it would require the entry to already exist in the old map. Shouldn't you simply have a sorted list of map entries?
Actually, especially in Java 7 onwards, even your second method is not going to work. (See below for why.) Anyway, the reason is that maps must have distinct keys, and when you are using your value as key, two equal values would be treated as equal keys.
The proper fix, by the way, is to sort by value, then by key:
int compare = map.get(k1).compareTo(map.get(k2));
if (compare == 0) {
compare = k1.compareTo(k2);
}
return compare;
Proper comparators must follow three rules:
compare(a, a) == 0 for all values of a.
signum(compare(a, b)) == -signum(compare(b, a)) for all values of a and b.
if signum(compare(a, b)) == signum(compare(b, c)), then signum(compare(a, c)) must also have the same value, for all values of a, b, and c.
I am looking for a container that would basically work like HashMap where I could put and get any of the entries in O(1) time. I also want to be able to iterate through but I want the order to be sorted by values. So neither TreeMap nor LinkedHashMap would work for me. I found the example below:
SortedSet<Map.Entry<String, Double>> sortedSet = new TreeSet<Map.Entry<String, Double>>(
new Comparator<Map.Entry<String, Double>>() {
#Override
public int compare(Map.Entry<String, Double> e1,
Map.Entry<String, Double> e2) {
return e1.getValue().compareTo(e2.getValue());
}});
The problem is SortedSet doesn't have any get method to get entries.
I will be using this collection in a place where I will be adding entries but in case of already existing entry, the value(double) will be updated and then sorted again by using the comparator(which compares values as mentioned above). What can I use for my needs?
There is no such data structure in the Java class libraries.
But you could create one that is a wrapper for a private HashMap and a private TreeMap with the same set of key/value pairs.
This gives a data structure that has get complexity of O(1) like a regular HashMap (but not put or other update operations), and a key Set and entry Set which can be iterated in key order ... as requested in the original version of this Question.
Here is a start for you:
public class HybridMap<K,V> implements Map<K,V> {
private HashMap<K,V> hashmap = ...
private TreeMap<K,V> treemap = ...
#Override V get(K key) {
return hashmap.get(key);
}
#Override void (K key, V value) {
hashmap.put(key, value);
treemap.put(key, value);
}
// etcetera
}
Obviously, I've only implemented some of the (easy) methods.
If you want to be able to iterate the entries in value order (rather than key order), once again there is no such data structure in the Java class libraries.
In this case, wrapping a HashMap and TreeMap is going to be very complicated if you need the resulting Map to conform fully to the API contract.
So I suggest that you just use a HashMap and TreeSet of the key/value pairs ... and manually keep them in step.
I want to sort a Java TreeMap based on some attribute of value. To be specific, I want to sort a TreeMap<Integer, Hashset<Integer>> based on the size of Hashset<Integer>. To achieve this, I have done the following:
A Comparator class:
private static class ValueComparer implements Comparator<Integer> {
private Map<Integer, HashSet<Integer>> map = null;
public ValueComparer (Map<Integer, HashSet<Integer>> map){
super();
this.map = map;
}
#Override
public int compare(Integer o1, Integer o2) {
HashSet<Integer> h1 = map.get(o1);
HashSet<Integer> h2 = map.get(o2);
int compare = h2.size().compareTo(h1.size());
if (compare == 0 && o1!=o2){
return -1;
}
else {
return compare;
}
}
}
A usage example:
TreeMap<Integer, HashSet<Integer>> originalMap = new TreeMap<Integer, HashSet<Integer>>();
//load keys and values into map
ValueComparer comp = new ValueComparer(originalMap);
TreeMap<Integer, HashSet<Integer>> sortedMap = new TreeMap<Integer, HashSet<Integer>>(comp);
sortedMap.putAll(originalMap);
The problem:
This doesn't work when originalMap contains more than 2 values of the same size. For other cases, it works alright. When more than two values in the map are of same size, the third value in the new sorted-map is null and throws NullPointerException when I try to access it.
I can't figure out what the problem is. Woule be nice if someone could point out.
Update:
Here's an example that works when two values have the same size: http://ideone.com/iFD9c
In the above example, if you uncomment lines 52-54, this code will fail- that's what my problem is.
Update: You cannot return -1 from ValueComparator just because you want to avoid duplicate keys to not be removed. Check the contract of Comparator.compare.
When you pass a Comparator to TreeMap you compute a ("new") place to put the entry. No (computed) key can exist more than once in a TreeMap.
If you want to sort the orginalMap by size of the value you can do as follows:
public static void main(String[] args) throws Exception {
TreeMap<Integer, HashSet<Integer>> originalMap =
new TreeMap<Integer, HashSet<Integer>>();
originalMap.put(0, new HashSet<Integer>() {{ add(6); add(7); }});
originalMap.put(1, new HashSet<Integer>() {{ add(6); }});
originalMap.put(2, new HashSet<Integer>() {{ add(9); add(8); }});
ArrayList<Map.Entry<Integer, HashSet<Integer>>> list =
new ArrayList<Map.Entry<Integer, HashSet<Integer>>>();
list.addAll(originalMap.entrySet());
Collections.sort(list, new Comparator<Map.Entry<Integer,HashSet<Integer>>>(){
public int compare(Map.Entry<Integer, HashSet<Integer>> o1,
Map.Entry<Integer, HashSet<Integer>> o2) {
Integer size1 = (Integer) o1.getValue().size();
Integer size2 = (Integer) o2.getValue().size();
return size2.compareTo(size1);
}
});
System.out.println(list);
}
Your comparator logic (which I'm not sure I follow why you'd return -1 if the set sizes are equal but they keys are different) shouldn't affect what the Map itself returns when you call get(key).
Are you positive you aren't inserting null values into the initial map? What does this code look like?
Your comparator doesn't respect the Comparator contract: if compare(o1, o2) < 0, then compare(o2, o1) should be > 0. You must find a deterministic way of comparing your elements when both sizes are the same and the integers are not identical. You could perhaps use the System.identityHashCode() of the integers to compare them in this case.
That said, I really wonder what you could do with such a map: you can't create new Integers and use them to get a value out of the map, and you can't modify the sets that it holds.
Side note: your comparator code sample uses map and data to refer to the same map.
You can have TreeMap ordered only by keys. There is no way of creating TreeMap ordered by values, because you will get StackOverflowException.
Think about it. To get an element from a tree, you need to perform comparisions, but to perform comparisions, you need to get elements.
You will have to sort it in other collection or to use Tree, you will have to encapsulate the integer (from entry value) also into the entry key and define comparator using that integer taken from a key.
Assuming you cannot use a comparator that returns 0 with a Set, this might work: Add all the elements in originalMap.entrySet() to an ArrayList and then sort the ArrayList using your ValueComparer, changing it to return 0 as necessary.
Then add all the entries in the sorted ArrayList to a LinkedHashMap.
I had a similar problem as the original poster. I had a TreeMap i wanted to sort on a value. But when I made a comparator that looked at the value, i had issues because of the breaking of the comparator that JB talked about. I was able to use my custom comparator and still observe the contract. When the valuse I was looking at were equal, i fell back to comparing the keys. I didn't care about the order if values were equal.
public int compare(String a, String b) {
if(base.get(a)[0] == base.get(b)[0]){ //need to handle when they are equal
return a.compareTo(b);
}else if (base.get(a)[0] < base.get(b)[0]) {
return -1;
} else {
return 1;
} // returning 0 would merge keys
I've made a BinaryTree< HashMap<String, String> >.
How can I compare the two keys so I can correctly insert the two elements (HashMaps) into the ordered BinaryTree?
Here's what I've got so far:
public class MyMap<K extends Comparable<K>, V> extends HashMap<K, V> implements Comparable< MyMap<K, V> >
{
#override
public int compareTo(MyMap<K, V> mapTwo)
{
if ( (this.keySet().equals(mapTwo.keySet())) ) return 0;
//How can I check for greater than/less than and keep my generics?
}
EDIT: There is only one key in each HashMap (it's a very simple language translation system), so sorting the keys shouldn't be necessary. I would have liked to use the String.compareTo() method, but because of my generics, the compiler doesn't know that K is a String
I think you've picked a bad data structure.
HashMaps are not naturally ordered. The keys in the set for a HashMap have an unpredictable order that is sensitive to the sequence of operations that populated the map. This makes it unsuitable for comparing two HashMaps.
In order to compare a pair of HashMaps, you need to extract the respective key sets, sort them and then compare the sorted sets. In other words, a compareTo method for HashSet derived classes is going to be O(NlogN) on average.
FWIW, a compareTo implementation would look something like this, assuming that the method is to order the HashMaps based on the sorted lists keys in their respective key sets. Obviously, there are other orderings based on the key sets.
public int compareTo(MyMap<K, V> other) {
List<K> myKeys = new ArrayList<K>(this.keySet());
List<K> otherKeys = new ArrayList<K>(other.keySet());
Collections.sort(myKeys);
Collections.sort(otherKeys);
final int minSize = Math.min(myKeys.size(), otherKeys.size());
for (int i = 0; i < minSize; i++) {
int cmp = myKeys.get(i).compareTo(otherKeys.get(i));
if (cmp != 0) {
return cmp;
}
}
return (myKeys.size() - otherKeys.size());
}
If there is only ever one key / value pair in the map, then you should replace it with a simple Pair<K,V> class. Using a HashMap to represent a single pair is ... crazy.
i have the following TreeMap:
TreeMap<Integer, Double> map;
the Double values are not unique.
i iterate through the map using Integer keys and the functions firstEntry() and higherEntry() and modify the Double values.
Now i want to list the values of the pairs in the order of decreasing Double values.
what is the best way to do this?
those Integer keys are important to me and because the Double values are not unique, i cannot have a Double key.
Update:
More Explanation
it is the classic problem. lets say rollnos of students is the key and their percentage is the value. now sort by percentage and then we should be able to tell whose percentage is it. therefore i need the integer key.
The obvious solution is to obtain a collection of the doubles (possibly via the entrySet and then getValue - the TreeMap class has a values() method, you can just use that), and proceed to sort them (using Collections.sort or Arrays.sort) - this would, however, take O(n logn) time.
I'm not sure you can do it in a smarter (== faster) way, unless you completely change the data structure. However, the only way in which I see this happening with another data structure is keeping a wrapper over the integer and the double and writing two comparators - one which compares the integer and one which compares first by the double and then by the integer. The original TreeMap you're using would be the same but you would be able to detach another TreeMap from it, sorted by the second comparator. Detaching would still take O(n logn) time though.
you can build a TreeSet, that guarantees insertion order:
#Test
public void treeMapSortedByValue() {
// given the following map:
TreeMap<Integer, Double> map = new TreeMap<Integer, Double>();
map.put(2, Math.E);
map.put(1, Math.PI);
map.put(3, 42.0);
// build a TreeSet of entries
Set<Map.Entry<Integer, Double>> sortedEntries = new TreeSet<Map.Entry<Integer, Double>>(new DoubleComparator());
sortedEntries.addAll(map.entrySet());
// optionally you can build a List<Double> with the sorted
List<Double> doubles = new LinkedList<Double>();
for (Map.Entry<Integer, Double> entry : sortedEntries) {
doubles.add(entry.getValue());
}
}
this should give you: [2.718281828459045, 3.141592653589793, 42.0] (nb: [Math.E, Math.PI, Math.UNIVERSAL_ANSWER] :-).
PS
the Comparator:
class DoubleComparator implements Comparator<Map.Entry<Integer, Double>> {
#Override
public int compare(Entry<Integer, Double> o1, Entry<Integer, Double> o2) {
return Double.compare(o1.getValue(), o2.getValue());
}
}
What you can do is the following : use entrySet to iterate through the entries. Put them into a list. Sort the date with the right comparator then.