TreeMap<int[],Double> initialisation and sorting by value - java

I have to make sorted map which key is int[] and value is double. It cant be swapped because double will be duplicated. Moreover, map will be sort by value and last x values will be deleted.
I tried to make
Map<int[],Double> map = new TreeMap<>();;
int[] i = {0,1,1,0};
map.put(i,8.5); // ERROR HERE Organisms.java:46
i = new int[]{0,0,0,0};
map.put(i,30.0);
System.out.println("sorted" + sortByValue(map));
Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException:
[I cannot be cast to java.lang.Comparable at
java.util.TreeMap.compare(TreeMap.java:1294) at
java.util.TreeMap.put(TreeMap.java:538) at
com.pszt_organism.Organisms.test(Organisms.java:46) <-- MARKED ERROR
I found method sortByValue(Map<K, V> map) in this topic: java8 example by Carter Page
I suppose that TreeMap has problem with sorting table of int. How to solve that?
EDIT:
private <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
return map.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(/*Collections.reverseOrder()*/))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
}

The problem is that Java array types do not implement Comparable.
The solution: implement a Comparator that will compare your int[] keys, and pass an instance as a parameter to the TreeMap constructor.
However, this will only work if the int[] objects are not mutated while they are in use as keys. If you mutate them, then you will "break" the TreeMap and Map operations will behave incorrectly.
You could also wrap the int[] objects in a class that implements Comparable, implement compareTo, equals and hashCode. The same caveat about mutation applies with this approach as well.

int[] arrays as poorly suited for use as keys in a Map (see this Q&A for a brief explanation; it talks about array lists, but the same logic applies to arrays as well).
If you are set on using arrays as keys anyway, proceed with caution:
Since int[] is not Comparable, you cannot use it as a key without supplying a piece of logic for comparing arrays.
Here is how you can do it:
Map<int[],Double> map = new TreeMap<>(
new Comparator<int[]>() {
#Override public int compare(int[] lhs, int[] rhs) {
int len = Math.min(lhs.length, rhs.length);
for (int i = 0 ; i != len ; i++) {
if (lhs[i] != rhs[i]) {
return Integer.compare(lhs[i], rhs[i]);
}
}
// If we're here, common elements match up;
// hence, the longer of the two arrays wins.
return Integer.compare(lhs.length, rhs.length)
}
}
);

You don't need to use TreeMap to use that sorting method. Just use a different Map. That sorting method creates a new LinkedHashMap for the result, so the Map which is passed as an argument is just a temporary container.

Related

How do I take a list, collect it to a map, then sort using Java Streams?

I have the following code....
public Map<Object, Integer> getRankings(){
Stream<String> stream = votes.stream();
return stream
.collect(Collectors.toMap(s -> s, s -> 1, Integer::sum));
}
This works great but now I want to sort the map based on count. I tried a sort after the collect but the method isn't available because the stream is now a map. How would I sort this before returning it? Can I do it using a Stream or do I have to sort as a map?
Maps are fundamentally unsorted. You cannot therefore imply an order.
However, some specific subclasses of map do include a definition of ordering. The point is, java.util.Map itself does not, so we must delve into the subclasses; For example, a new java.util.HashMap cannot be ordered, period.
LinkedHashMap is sorted by 'insertion order', and thus, you can enforce an order for it.
SortedMap classes (such as TreeMap) are sorted on key, with a comparator, and therefore it is not possible to sort them on the value either.
The general answer is that you had some problem X and thought: I know! I'll fix this unknown problem X by having a map that is ordered by value! But it is highly unlikely that is a good way to solve X. Unfortunately, you didn't say what X is, you are merely asking questions about how to get a map sorted on values in java, and thus we're now stuck on: Ooooof, that is really hard to do.
Just in case you still think this 'map ordered on values' idea of yours is how to solve X:
There is no .toLinkedHashMap. There IS a variant of Collectors.toMap with a fourth argument which is a lambda that makes the map for you. Use that one, passing in a lambda that makes a new LinkedHashMap. Then you need to coerce the stream API to insert in the right order, which is impossible.
So, we need to make that possible: First collect to a plain jane map. Then re-spin that back out as a stream by asking that map for its entryset and streaming over it. Then, turn that stream into a sorted stream, sorting on entry.getValue(). Then collect the resulting sequential sorted stream back into a map, using one of the Collectors.toMap methods that let you provide the mapSupplier. You don't need the grouping toMap here - your stream objects are ready to be inserted verbatim.
This is of course, inefficient and has an intermediate stage map which is then immediately tossed in the garbage. But it's the only way to do this, which goes back: Having maps-sorted-on-value is rather tricky.
This solution works but I would give the check to a more "streamy" Way.
public Map<Object, Integer> getRankings(){
Stream<String> stream = votes.stream();
Map<Object, Integer> map = stream
.collect(Collectors.toMap(s -> s, s -> 1, Integer::sum));
return Vote.sortByValues(map);
}
public static <K, V extends Comparable<V>> Map<K, V> sortByValues(final Map<K, V> map) {
Comparator<K> valueComparator = new Comparator<K>() {
public int compare(K k1, K k2) {
int compare = map.get(k2).compareTo(map.get(k1));
if (compare == 0) return 1;
else return compare;
}
};
Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}

Why Java needs an argument for a method to correctly deduce generics?

Suppose we have the following code:
public class Main {
public static void main(String[] args) {
}
private static <K, V extends Comparable<V>> Map<K, V> sorted(Map<K, V> map) {
return map.entrySet()
.stream()
.sorted(Comparator.comparing(Entry::getValue))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
}
A fairly simple snippet - we take a map and return another map that represents the same map, but sorted according to its values.
Let's change the functionality a little bit - instead of returning something based on an argument, let's introduce and work on a static object:
public class Main {
public static void main(String[] args) {
}
private static Map<Integer, String> map = new HashMap<>();
private static <K, V extends Comparable<V>> Map<K, V> sorted() {
return map.entrySet()
.stream()
.sorted(Comparator.comparing(Entry::getValue))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
}
Note that the only thing we changed was getting rid of the argument and, instead, working with a static field. One may wonder why then introduce the generics K and V at all - after all they will always be Integer and String, respectively. But wouldn't that mean that Java can easily deduce those arguments? Why exactly does it fail with an error of:
Error:(16, 25) java: incompatible types: inference variable K has incompatible bounds
equality constraints: K,K
lower bounds: java.lang.Integer
I have seen some questions regarding similar error messages, but I couldn't get any information from there. Why exactly does this fail? It's maybe worth to mention that while the compiler produces the above error, when using IntelliJ IDEA, the Entry::getKey and Entry::getValue method references are highlighed in red and, when hovered over, the following message gets displayed:
Non-static method cannot be referenced from a static context.
Which is weird, since I see nothing alike in the actual error message from javac.
The concrete type arguments are determined at the call site.
For example, any of these are legal calls:
Map<Void, Integer> mapA = Main.sorted();
Map<String, String> mapB = Main.sorted();
Map<Integer, GregorianCalendar> mapC = Main.sorted();
...but, given the method definition, they would all return the same value; since they are of different types, at least two of mapA..C are type-incorrect (and, in fact, all three are).
The sole point of generics is to allow you to omit explicit casts; this is advantageous, because the compiler is able to reason better than you about whether a cast (or, rather, all the casts throughout the code) are safe.
So, if you write that a method returns a Map<Void, Integer>, or whatever, the compiler is going to do everything it can to make sure that it really does return a Map<Void, Integer>. Or, to put this another way, the compiler ensures that it returns a Map where every element of the keySet() can be safely cast to a Void; and every element of values() can be safely cast to an Integer.
Thus far I have only talked about concrete type parameters: Void, Integer, String, GregorianCalendar. But the compiler doesn't draw distinction between such type parameters and type variables. Thus, if you say that a method returns a Map<K, V>, the compiler is going to do everything it can to make sure that it returns a Map where every element of the keySet() can be cast to K, and every element of the values() can be cast to V.
The important point here is that this has to work for any K and any V, because, as stated in the first line of the question, the concrete values of K and V are determined at the call site.
There are only a small number of Ks that an Integer can be safely cast to (Integer, Number, Object, Serializable); a null Integer can be cast to any other type, but the compiler doesn't know if the Integers are null. So the compiler is simply saying "I can't be sure that these casts would be safe, so I'm going disallow them".
(A similar situation applies to V).
Your method only returns a safe value when K and V are Integer and String respectively, because map is a Map<Integer, String>. As such, you don't need type variables, as the method will always return a Map<Integer, String>.
private static Map<Integer, String> sorted() {
return map.entrySet()
.stream()
.sorted(Comparator.comparing(Entry::getValue))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
(or it could be a Map<Object, String>, or whatever. The point is that you don't need type variables).

TreeMap sort is not working on equal values

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.

Sorting Java TreeMap by value not working when more than two values have the same sort-property

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

Compare 2 keys in Java HashMap

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.

Categories

Resources