So I want to use TreeMap with a customized comparator
My key is a String: id, my value is an int: count;
I NEED TO COMPARE THE COUNT, AS THE VALUE(INTEGER) IN THE TREEMAP
So I have:
In one class:
import java.util.*;
public TreeMap<String, Integer> tm = new TreeMap<String, Integer>(new SortIdCount<Integer>());
In another class:
import java.util.Comparator;
public class SortIdCount implements Comparator<Integer>{
public int compare(Integer count1, Integer count2) {
return count1.compareTo(count2);
}
}
It shows error in eclipse:
The type SortIdCount is not generic; it cannot be parameterized with arguments <Integer>
The type SortIdCount is not generic; it cannot be parameterized with
arguments < Integer >
Reason : Class SortIdCount is not genric type so you can not pass parameterized argument.
Error At Line : (new SortIdCount<Integer>()
Note : A TreeMap is always sorted based on its keys, however if you
want to sort it based on its values then you can build a logic to do
this using comparator. Below is a complete code of sorting a TreeMap
by values.
To Sort on Values You can refer below code snippet.
import java.util.*;
public class TreeMapDemo {
//Method for sorting the TreeMap based on values
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(k1).compareTo(map.get(k2));
if (compare == 0)
return 1;
else
return compare;
}
};
Map<K, V> sortedByValues =
new TreeMap<>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
public static void main(String args[]) {
TreeMap<String, Integer> treemap = new TreeMap<>();
// Put elements to the map
treemap.put("Key1", 5);
treemap.put("Key2", 4);
treemap.put("Key3", 3);
treemap.put("Key4", 2);
treemap.put("Key5", 1);
// Calling the method sortByvalues
Map sortedMap = sortByValues(treemap);
// Get a set of the entries on the sorted map
Set set = sortedMap.entrySet();
// Get an iterator
Iterator i = set.iterator();
// Display elements
while(i.hasNext()) {
Map.Entry me = (Map.Entry)i.next();
System.out.print(me.getKey() + ": ");
System.out.println(me.getValue());
}
}
}
For more details refer this answer
As others might already have mentioned, that the Comparator used in the TreeMap constructor is used to sort the map by key.
public TreeMap(Comparator comparator)
... ordered according to the given comparator. All keys inserted into the map must be mutually comparable by the given comparator: comparator.compare(k1, k2) must not throw a ClassCastException for any keys k1 and k2 in the map...
But if you want to sort a map by the value, still we have a solution using LinkedHashMap as:
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("hi", 1);
treeMap.put("there", 3);
treeMap.put("hey", 2);
treeMap = treeMap.entrySet().stream().sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldV, newV) -> oldV, LinkedHashMap::new));
System.out.println(treeMap);
Output:
{there=3, hey=2, hi=1}
You can't have a TreeMap which sort by value. There are so many ways to sort a map by value but I believe TreeMap is not for that. If you look on the TreeMap constructor: TreeMap(Comparator<? super K> comparator) which takes comparator as an argument which's generic type clearly should be the super of Key of the Map. So it impossible to send a Comparator<Integer> when the key is String.
Please follow the answer for sorting Map by value.
The comparator used for a TreeMap has to compare the keys, not the values.
So the comparison would have to take the passed key and look up the value in the map. And there you have a classical hen (map) and egg (comparator) problem. You need the comparator to create the map, but since the comparator has to look into the map, you need the map for the comparator. You could solve this by having comparator.setMap(map) after creation of the map.
But the most critical problem is, that your map will not work. As long as the entry is not written to the map, you don't have the value, so the comparator won't work. Only idea I have for that is using two maps. Both String, Integer. The first one is simply used for the lookup in the comparator. So you always have to put into the first and then into the second.
And then you still have problems, because you might easily violate the SortedMap contract. A key must not change (regarding the sorting) after it is inserted into the map. But as soon as you put another value, you have a different sorting of the key.
So I'd really rethink your requirements and whether a TreeMap is the best solution to what you need.
public class SortIdCount implements Comparator<String> {
public int compare(String count1, String count2) {
return count1.compareTo(count2);
}
}
Related
I'm implementing a NavigableMap-implementing LinkedHashMap in Java. (There don't seem to be many (any?) reasons why LinkedHashMap doesn't implement NavigableMap already, but I digress...)
I've written lowerKey(), lowerEntry(), higherKey(), and higherEntry() by iterating the entrySet(). I don't see any way in these cases to avoid iterating the entire entrySet().
For floorKey(), floorEntry(), ceilingKey(), and ceilingEntry(), in the case that the key exists, I'd like to avoid the expense of iterating the entrySet(), considering that I can already get the value with plain-old get().
Is there a way to get the Map.Entry for a particular key, rather than just the value? Thanks.
You have the key, and you can get the value associated with the key using get, now all you gotta do is to make a Map.Entry, and we can do that with the Map.entry factory method:
var value = theBackingLinkedHashMap.get(key);
if (value == null) {
return null;
}
return Map.entry(key, value);
The entry returned by entry does have two caveats that you should be aware of:
does not allow null keys, so your NavigableLinkedHashMap would need to not allow null keys either
is immutable, so you cannot call setValue.
But other than that, it will work as if you got the Map.Entry from inside the backing LinkedHashMap, and it does fulfil the contract of ceilingEntry, floorEntry etc, since they just ask for a "a key-value mapping", and doesn't require that it has to have the mutability as the map itself or anything like that. For example, this is ceilingEntry:
Returns a key-value mapping associated with the least key greater than or equal to the given key, or null if there is no such key.
I would use TreeSet to keep the keys in the NavigableMap class. See example below:
import java.util.*;
public class Main{
public static void main(String[] args) {
NavMap<Integer, String> map = new NavMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(10, "ten");
System.out.println(map.lowerKey(3)); //2
System.out.println(map.higherKey(3)); //4
System.out.println(map.higherEntry(3)); // 4:four
System.out.println(map.ceilingKey(7)); //10
System.out.println(map.floorKey(7)); //4
System.out.println(map.floorEntry(10)); // 4:four
}
}
class NavMap<K extends Comparable,V extends Comparable> extends LinkedHashMap<K,V>{
private TreeSet<K> keys = new TreeSet<>();
public Set<K> keySet(){ return keys; }
public K lowerKey (K k){ return keys.lower(k); }
public K higherKey (K k){ return keys.higher(k); }
public K floorKey (K k){ return keys.floor(k); }
public K ceilingKey(K k){ return keys.ceiling(k);}
public Map.Entry<K,V> lowerEntry(K k) { return newEntry(lowerKey(k), get(lowerKey(k)));}
public Map.Entry<K,V> higherEntry(K k) { return newEntry(higherKey(k), get(higherKey(k)));}
public Map.Entry<K,V> floorEntry(K k) { return newEntry(floorKey(k), get(floorKey(k)));}
public Map.Entry<K,V> ceilingEntry(K k) { return newEntry(ceilingKey(k),get(ceilingKey(k)));}
private Map.Entry<K,V> newEntry(K k, V v) { return new AbstractMap.SimpleEntry<>(k,v);}
public V put(K key, V value){
keys.add(key);
return super.put(key, value);
}
}
Output:
2
4
4=four
10
4
10=ten
#Sweeper's comment on his answer got me thinking. The solution I came up with was to maintain a Map from Key to Entry inside my class. That way I have a way of having O(1) access to the entrySet. Like so:
Map<K, Map.Entry<K, V>> entryMap = new HashMap<>();
for(final currentEntry : entrySet())
{
entryMap.put(currentEntry.getKey(), currentEntry);
}
I just need to update this Map every time an operation runs which changes the keySet. I only need to update the one entry in this Map that would be affected. That sounds like it wouldn't be very expensive.
You could do something like this.
Map<String,Integer> map = Map.of("Foo", 123, "Bar", 234);
Function<String, Entry<String,Integer>> getEntry =
getEntryFnc(map);
System.out.println(getEntry.apply("Foo"));
System.out.println(getEntry.apply("Bar"));
System.out.println(getEntry.apply("Baz"));
prints
Foo=123
Bar=234
Baz=null
Returns a lambda which builds an entry using the supplied key and map.
public static <K,V> Function<K, Entry<K,V>> getEntryFnc(Map<K,V> map) {
return key->
new AbstractMap.SimpleEntry<>(key, map.get(key));
};
}
I am using a treemap but created my own comparator so that the treemap is ordered by the values rather than the keys. This works fine but whenever I come to overwrite a <key, value> mapping, instead of being overwritten, a new mapping is added with the same key (which shouldn't happen because maps in Java are meant to have unique keys). I have even tried to remove the mapping first before adding another one but nothing gets deleted from the treemap. When I remove the comparator, there are no unique values and the treemap works as expected. Why does this happen?
Here is my code:
public Map<String, List<String>> mapQtToNonSampledCase(List<Entry> cases, Map<String, Integer> populationDistribution) {
Map<String. Integer> distribution = new HashMap<>(populationDistribution);
Map<String. List<String>> qtToCases = new HashMap<>();
Comparator<String> valueComparator = new Comparator<String>() {
public int compare(String k1, String k2) {
int compare = distribution.get(k1).compareTo(distribution.get(k2));
if (compare == 0)
return 1;
else
return compare;
}
};
TreeMap<String, Integer> sortedByValues = new TreeMap<>(valueComparator);
sortedByValues.putAll(distribution);
for(Entry entry: cases) {
List<Map.Entry<String, Integer>> listEntries = sortedByValues.entrySet().stream().collect(Collectors.tolist());
Map.Entry<String, Integer> qt = sortedByValues.firstEntry().getKey().equals(entry.get(UtilsClass.ID).toString()) ? (listEntries.get(1) != null ? listEntries.get(1) : null) : sortedByValues.firstEntry();
if(qt != null) {
if(!qtToCases.containsKey(qt.getKey()) {
qtToCases.put(qt.getKey(), new ArrayList<>());
);
}
qtToCases.get(qt.getKey()).add(entry.get(UtilsClass.ID).toString());
sortedByValues.put(qt.getKey(), qt.getValue() - 1);
}
}
// Printing keys
for(Map.Entry<String, Integer> entry : sortedByValues.entrySet()) {
System.out.println(entry.getKey());
}
}
And here is the console output (apologies for the quality, it's a picture from another device):
Your custom comparator is not consistent with equals: When you try to update a key with a different value, your comparator will return a value != 0, but the keys are the same.
See this comment in TreeMap API doc:
Note that the ordering maintained by a tree map, like any sorted map,
and whether or not an explicit comparator is provided, must be
consistent with equals if this sorted map is to correctly implement
the Map interface.
The term 'consistent with equals' is defined here: [Comparable API doc]:2
The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C.
I am writing the below code to sort the hash map values :
private static HashMap sortByValues(HashMap map) {
List list = new LinkedList(map.entrySet());
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return (((Map.Entry) (o1)).getValue()).compareTo(((Map.Entry) (o2)).getValue());
}
});
}
However When I execute this, it throws an error stating cannot find symbol compareTo. But isnt the method in the String class which is in the lang package?
Also when i replace it by adding a Comparable typecast, it runs fine
private static HashMap sortByValues(HashMap map) {
List list = new LinkedList(map.entrySet());
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
return ((Comparable) ((Map.Entry) (o1)).getValue()).compareTo(((Map.Entry) (o2)).getValue());
}
});
}
Can someone please help, I am a beginner in Collections.
All of Java's collection classes and interfaces are generics, which means they are intended to be used with type parameters. For historical reasons it's possible to use them without type parameters, which is what you've done here. However, it's a bad idea to do that, because
you sacrifice a lot of the type safety that the compiler gives you,
you end up casting objects to whatever type you need them to be, which is prone to error, and such errors usually only reveal themselves at run time.
Finding errors at compile time is better than finding them at run time. The compiler is your friend - it gives you messages to help you find your errors.
Now in this particular case, you seem to be building a sorted list of values from your map. But it only makes sense to do this if the values in your map belong to some type that can be sorted. There's no general way of sorting Object, so for this to make sense, you want to restrict your parameter to be a map whose values can be sorted, or to put it another way, can be compared to other objects of the same type.
The generic interface that tells you that one object can be compared to another is Comparable. So if you have a type V, then writing V extends Comparable<V> means that one object of type V can be compared to other objects of type V, using the compareTo method. This is the condition that you want the type of the values in your map to obey. You don't need any such condition on the type of the keys in your map.
Therefore, you could write your method as generic, which means that its signature will list some type parameters, inside < > characters, possibly with some conditions on those type parameters. In your case, you'd give your method a signature like this, assuming it's going to return a List.
private static <K, V extends Comparable<V>> List<V> sortAndListValues(Map<K,V> map)
Of course, if you really intend to return some kind of sorted map, then it might be more like
private static <K, V extends Comparable<V>> Map<K,V> sortByValues(Map<K,V> map)
but you need to remember that it's not possible to sort HashMap objects. They're naturally sorted in an order that's implied by the hashCode function of the key class, and by the current size of the map. This is generally not a very useful order. There are other types of map in the JDK, such as
TreeMap, which sorts its entries according to the key - not what you want here
LinkedHashMap, which sorts its entries according to the order they were inserted - and you could probably make use of this here.
For the sake of answering your question though, I'm just going to write the List version of your method.
private static <K, V extends Comparable<V>> List<V> sortAndListValues(Map<K,V> map) {
List<V> toReturn = new LinkedList<>(map.values());
Collections.sort(toReturn, new Comparator<V>() {
public int compare(V first, V second) {
return first.compareTo(second);
}
});
return toReturn;
}
Note that by using the type parameters K and V wherever it's appropriate to do so, there's no need for any kind of casting. The compiler will also warn you if you try to use any of the objects in the map in a way that's inappropriate for their type.
There are shorter ways of writing this of course, using the "functional style" that comes with Java 8. But that's a topic for another post entirely.
#ghostrider - you have removed generics from HashMap so both key and value are of Object type. Inside contents of map are Comparable type but the reference is of Entry<Object, Object> not Entry<Object, Comparable>. Look into the below example.
Object obj = new Integer(5);
int i = obj.intValue(); // Error
int i = ((Integer)obj).intValue(); // Success
Here int i = obj.intValue(); fails but int i = ((Integer)obj).intValue(); get success because i am explicitly type casting because of reference is of Object type.
You can do this by following
private static Map<String, Integer> sortByValue(Map<String, Integer> unsortMap) {
// 1. Convert Map to List of Map
List<Map.Entry<String, Integer>> list =
new LinkedList<Map.Entry<String, Integer>>(unsortMap.entrySet());
// 2. Sort list with Collections.sort(), provide a custom Comparator
// Try switch the o1 o2 position for a different order
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1,
Map.Entry<String, Integer> o2) {
return (o1.getValue()).compareTo(o2.getValue());
}
});
// 3. Loop the sorted list and put it into a new insertion order Map LinkedHashMap
Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
for (Map.Entry<String, Integer> entry : list) {
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
I want to write a comparator that will let me sort a TreeMap by value instead of the default natural ordering.
I tried something like this, but can't find out what went wrong:
import java.util.*;
class treeMap {
public static void main(String[] args) {
System.out.println("the main");
byValue cmp = new byValue();
Map<String, Integer> map = new TreeMap<String, Integer>(cmp);
map.put("de",10);
map.put("ab", 20);
map.put("a",5);
for (Map.Entry<String,Integer> pair: map.entrySet()) {
System.out.println(pair.getKey()+":"+pair.getValue());
}
}
}
class byValue implements Comparator<Map.Entry<String,Integer>> {
public int compare(Map.Entry<String,Integer> e1, Map.Entry<String,Integer> e2) {
if (e1.getValue() < e2.getValue()){
return 1;
} else if (e1.getValue() == e2.getValue()) {
return 0;
} else {
return -1;
}
}
}
I guess what am I asking is: Can I get a Map.Entry passed to the comparator?
You can't have the TreeMap itself sort on the values, since that defies the SortedMap specification:
A Map that further provides a total ordering on its keys.
However, using an external collection, you can always sort Map.entrySet() however you wish, either by keys, values, or even a combination(!!) of the two.
Here's a generic method that returns a SortedSet of Map.Entry, given a Map whose values are Comparable:
static <K,V extends Comparable<? super V>>
SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
#Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
return res != 0 ? res : 1;
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
Now you can do the following:
Map<String,Integer> map = new TreeMap<String,Integer>();
map.put("A", 3);
map.put("B", 2);
map.put("C", 1);
System.out.println(map);
// prints "{A=3, B=2, C=1}"
System.out.println(entriesSortedByValues(map));
// prints "[C=1, B=2, A=3]"
Note that funky stuff will happen if you try to modify either the SortedSet itself, or the Map.Entry within, because this is no longer a "view" of the original map like entrySet() is.
Generally speaking, the need to sort a map's entries by its values is atypical.
Note on == for Integer
Your original comparator compares Integer using ==. This is almost always wrong, since == with Integer operands is a reference equality, not value equality.
System.out.println(new Integer(0) == new Integer(0)); // prints "false"!!!
Related questions
When comparing two Integers in Java does auto-unboxing occur? (NO!!!)
Is it guaranteed that new Integer(i) == i in Java? (YES!!!)
polygenelubricants answer is almost perfect. It has one important bug though. It will not handle map entries where the values are the same.
This code:...
Map<String, Integer> nonSortedMap = new HashMap<String, Integer>();
nonSortedMap.put("ape", 1);
nonSortedMap.put("pig", 3);
nonSortedMap.put("cow", 1);
nonSortedMap.put("frog", 2);
for (Entry<String, Integer> entry : entriesSortedByValues(nonSortedMap)) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
Would output:
ape:1
frog:2
pig:3
Note how our cow dissapeared as it shared the value "1" with our ape :O!
This modification of the code solves that issue:
static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
#Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
return res != 0 ? res : 1; // Special fix to preserve items with equal values
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
In Java 8:
LinkedHashMap<Integer, String> sortedMap = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(/* Optional: Comparator.reverseOrder() */))
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1, LinkedHashMap::new));
A TreeMap is always sorted by the keys, anything else is impossible. A Comparator merely allows you to control how the keys are sorted.
If you want the sorted values, you have to extract them into a List and sort that.
This can't be done by using a Comparator, as it will always get the key of the map to compare. TreeMap can only sort by the key.
Olof's answer is good, but it needs one more thing before it's perfect. In the comments below his answer, dacwe (correctly) points out that his implementation violates the Compare/Equals contract for Sets. If you try to call contains or remove on an entry that's clearly in the set, the set won't recognize it because of the code that allows entries with equal values to be placed in the set. So, in order to fix this, we need to test for equality between the keys:
static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
#Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
if (e1.getKey().equals(e2.getKey())) {
return res; // Code will now handle equality properly
} else {
return res != 0 ? res : 1; // While still adding all entries
}
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
"Note that the ordering maintained by a sorted set (whether or not an explicit comparator is provided) must be consistent with equals if the sorted set is to correctly implement the Set interface... the Set interface is defined in terms of the equals operation, but a sorted set performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal."
(http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html)
Since we originally overlooked equality in order to force the set to add equal valued entries, now we have to test for equality in the keys in order for the set to actually return the entry you're looking for. This is kinda messy and definitely not how sets were intended to be used - but it works.
I know this post specifically asks for sorting a TreeMap by values, but for those of us that don't really care about implementation but do want a solution that keeps the collection sorted as elements are added, I would appreciate feedback on this TreeSet-based solution. For one, elements are not easily retrieved by key, but for the use case I had at hand (finding the n keys with the lowest values), this was not a requirement.
TreeSet<Map.Entry<Integer, Double>> set = new TreeSet<>(new Comparator<Map.Entry<Integer, Double>>()
{
#Override
public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> o2)
{
int valueComparison = o1.getValue().compareTo(o2.getValue());
return valueComparison == 0 ? o1.getKey().compareTo(o2.getKey()) : valueComparison;
}
});
int key = 5;
double value = 1.0;
set.add(new AbstractMap.SimpleEntry<>(key, value));
A lot of people hear adviced to use List and i prefer to use it as well
here are two methods you need to sort the entries of the Map according to their values.
static final Comparator<Entry<?, Double>> DOUBLE_VALUE_COMPARATOR =
new Comparator<Entry<?, Double>>() {
#Override
public int compare(Entry<?, Double> o1, Entry<?, Double> o2) {
return o1.getValue().compareTo(o2.getValue());
}
};
static final List<Entry<?, Double>> sortHashMapByDoubleValue(HashMap temp)
{
Set<Entry<?, Double>> entryOfMap = temp.entrySet();
List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
return entries;
}
import java.util.*;
public class Main {
public static void main(String[] args) {
TreeMap<String, Integer> initTree = new TreeMap();
initTree.put("D", 0);
initTree.put("C", -3);
initTree.put("A", 43);
initTree.put("B", 32);
System.out.println("Sorted by keys:");
System.out.println(initTree);
List list = new ArrayList(initTree.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
#Override
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
return e1.getValue().compareTo(e2.getValue());
}
});
System.out.println("Sorted by values:");
System.out.println(list);
}
}
//convert HashMap into List
List<Entry<String, Integer>> list = new LinkedList<Entry<String, Integer>>(map.entrySet());
Collections.sort(list, (o1, o2) -> o1.getValue().compareTo(o2.getValue()));
If you want to use a Hash map you can add a condition in Comparator to check by values first & if values are equal perform a sort on keys
HashMap<String , Integer> polpularity = new HashMap<>();
List<String> collect = popularity.entrySet().stream().sorted((t2, t1) -> {
if (t2.getValue() > t1.getValue()) {
return -1;
} else if (t2.getValue() < t1.getValue()) {
return +1;
} else {
return t2.getKey().compareTo(t1.getKey());
}
}).map(entry -> entry.getKey()).collect(Collectors.toList());
If you don't want to take care of the latter condition then use a Treemap which will offer you sorting by itself, this can be done in an elegant single line of code:
TreeMap<String, Integer> popularity = new TreeMap<>();
List<String> collect = popularity.entrySet().stream().sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).map(entry -> entry.getKey()).collect(Collectors.toList());
TreeMap is always sorted by the keys.
If you want TreeMap to be sorted by the values, so you can simply construct it also.
Example:
// the original TreeMap which is sorted by key
Map<String, Integer> map = new TreeMap<>();
map.put("de",10);
map.put("ab", 20);
map.put("a",5);
// expected output:
// {a=5, ab=20, de=10}
System.out.println(map);
// now we will constrcut a new TreeSet which is sorted by values
// [original TreeMap values will be the keys for this new TreeMap]
TreeMap<Integer, String> newTreeMapSortedByValue = new TreeMap();
treeMapmap.forEach((k,v) -> newTreeMapSortedByValue.put(v, k));
// expected output:
// {5=a, 10=de, 20=ab}
System.out.println(newTreeMapSortedByValue);
Only 1 Line Of Code Solution
Normal Order
map.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(x->{});
Reverse Order
map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).forEachOrdered(x -> {});
This question already has answers here:
Sort a Map<Key, Value> by values
(64 answers)
Closed 3 years ago.
I have a HashMap<Object, Student> where the Object is the ID of the Student, and the Student is an object from Student.
How can I resort the HashMap by the Students name, student->getName()?
HashMaps are intrinsically unordered and cannot be sorted.
Instead, you can use a SortedMap implementation, such as a TreeMap.
However, even a sorted map can only sort by its keys.
If you want to sort by the values, you'll need to copy them to a sorted list.
You might not be able to sort a HashMap, but you can certainly do something that provides the same effect. I was able to sort my HashMap <String, Integer> by descending value of the Integer by using the excellent code posted at the Javarevisited blog. The same principle would apply to a HashMap <String, String> object:
/*
* Java method to sort Map in Java by value e.g. HashMap or Hashtable
* throw NullPointerException if Map contains null values
* It also sort values even if they are duplicates
*/
public static <K extends Comparable,V extends Comparable> Map<K,V> sortByValues(Map<K,V> map){
List<Map.Entry<K,V>> entries = new LinkedList<Map.Entry<K,V>>(map.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<K,V>>() {
#Override
public int compare(Entry<K, V> o1, Entry<K, V> o2) {
return o1.getValue().compareTo(o2.getValue());
// to compare alphabetically case insensitive return this instead
// o1.getValue().toString().compareToIgnoreCase(o2.getValue().toString());
}
});
//LinkedHashMap will keep the keys in the order they are inserted
//which is currently sorted on natural ordering
Map<K,V> sortedMap = new LinkedHashMap<K,V>();
for(Map.Entry<K,V> entry: entries){
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
To call this method, I use:
Map<String, Integer> sorted = sortByValues(myOriginalHashMapObject);
Read more: http://javarevisited.blogspot.com/2012/12/how-to-sort-hashmap-java-by-key-and-value.html#ixzz2akXStsGj
Maps cannot be ordered by values. You can do this, though:
Collection<Student> students = map.values();
Collection.sort(new ArrayList<Student>(students)), new Comparator<Student>() {
public int compare(Student s1, Student s2) {
return s1.getName().compareTo(s2.getName());
}
});
Assuming, of course, that you need to iterate over the values. (Why else would you want it ordered like that?)
Good luck.
HashMaps cannot be sorted by their values. A Map is designed for constant time lookups based on the key, so ordering by values should not be necessary. If you need to sort by name, I suggest using a SortedSet and creating a comparator that sorts by the names.
class StudentComparator implements Comparator<Student> {
int compare(Student s1, Student s2) {
return s1.getName().compareTo(s2.getName());
}
}
If you need both a constant time lookup and a sorted-by-value set, then you may need to maintain a map and a set.
I would definitely use a New Class that will store the key and the Object.
Then you can put every element of the Map into an ArrayList in the form of this class, and finally use a comparator to sort the ArrayList, afterwards you simply build a new Map. Code will be something like this:
Map<Object, Student> valueMap = new LinkedHashMap<String, String>();
List<Student> pairValueList = new ArrayList<PairValue>();
PairValue p;
for (Map.Entry<Object, Student> entry : map.entrySet()) {
Object key = entry.getKey();
Student value = entry.getValue();
p = new PairValue(key, value);
pairValueList.add(p);
}
Collections.sort(pairValueList, new Comparator<PairValue>() {
#Override
public int compare(PairValue c1, PairValue c2) {
return c1.getLabel().compareTo(c2.getLabel());
}
});
for (PairValue pv : pairValueList) {
valueMap.put(pv.getValue(), pv.getStudent());
}
The PairValue class
class PairValue {
private Object value;
private Student student;
public PairValue(Object value, String student) {
this.value = value;
this.student= student;
}
public String getValue() {
return value;
}
public String getStudent() {
return student;
}
}
Thats the way I solved some similar problem I had in the past. Please Note that the returned map implementation needs to be a LinkedHashMap.