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 -> {});
Related
SortedMap<Integer, Long> newMap = new TreeMap(new MyComparator(result));
newMap.putAll(result);
System.out.println("new map ---> " + newMap);
MyComparator.java
package com.example.admin.app;
import java.util.Comparator;
import java.util.Map;
class MyComparator implements Comparator {
Map map;
public MyComparator(Map map){
this.map = map;
}
public int compare (Object o1, Object o2) {
return ((Long) map.get(o2)).compareTo((Long) map.get(o1));
}
}
While using treemap comparator, if values of 2 keys are same, comparator is considering only 1st value and ignoring second one.
Eg: Unsorted map -> {2=93085, 1=93254, 4=92928, 9=93164, 8=93085}
my actual result for the code written: {1=93254, 9=93164, 8=93085, 4=92928}
I need output like --> {1=93254, 9=93164, 8=93085, 2=93085, 4=92928}
Since key 2 and 8 have same values (93085), I'm getting only one. Someone please help.
It's a property of the TreeMap to treat keys as equal when the comparator reports them as equal (and maps do not support multiple equal keys in general).
As the specification says:
…a sorted map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal.
If you want to prevent the keys from disappearing when there is no ordering between them, you have to add a secondary ordering, to be used when the primary ordering considers them as equal. Since your map has comparable keys in the first place, you can utilize their natural order to get the desired result:
class MyComparator implements Comparator<Integer> {
final Map<Integer, Long> map;
public MyComparator(Map<Integer, Long> map){
this.map = map;
}
public int compare(Integer o1, Integer o2) {
int c = Long.compare(map.get(o2), map.get(o1));
return c != 0? c: o2.compareTo(o1);
}
}
Map<Integer, Long> result = new HashMap<>();
result.put(2, 93085L);
result.put(1, 93254L);
result.put(4, 92928L);
result.put(9, 93164L);
result.put(8, 93085L);
SortedMap<Integer, Long> newMap = new TreeMap<>(new MyComparator(result));
newMap.putAll(result);
// new map ---> {1=93254, 9=93164, 8=93085, 2=93085, 4=92928}
System.out.println("new map ---> " + newMap);
Alternatively, you may use a LinkedHashMap which maintains the insertion order and fill it using a sorted list:
List<Integer> list = new ArrayList<>(result.keySet());
Collections.sort(list, new MyComparator(result));
Map<Integer, Long> newMap = new LinkedHashMap<>();
for(Integer i: list) newMap.put(i, result.get(i));
System.out.println("new map ---> " + newMap);
Both approaches produce a map with the desired iteration order. Which is more suitable depends on how it is subsequently used.
Since sorting a list does not eliminate duplicates, it would also work with your initial comparator, though I would make it type safe:
class MyComparator implements Comparator<Integer> {
final Map<?, Long> map;
public MyComparator(Map<?, Long> map){
this.map = map;
}
public int compare(Integer o1, Integer o2) {
return Long.compare(map.get(o2), map.get(o1));
}
}
But then, the relative order of entries with the same value is unspecified.
Have a Map<String, Integer> and trying to sort on value and length of String. I am trying to compare two different things in the statement so don't know if I need two different statements. This is being used to compare digit root so the String length and then the digit root is the value and the value.
For example:
("103",4); (1+0+3 == 4)
("4",4); (4 ==4)
("11101",4); (1+1+1+0+1 == 4)
("5",5); (5 == 5 )
("1003",4); (1+0+0+3 == 4)
But ("103",4) > ("4",4) because the length of "103" > "4", and ("11101",4) > ("103",4);, length "11101" > "103"
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) { int length = o1.getKey().length().compareTo(o2.getKey().length());
if(length != 0) {
return length;
}
return (o1.getValue()).compareTo(o2.getValue());
}
});
Edit Answered to the above Question(Also, response given)
Map<String,Integer> unsortMap = new.
TreeMap<String,Integer>();
unsortMap.put("103",4);
unsortMap.put("4",4);
unsortMap.put("11101",4);
unsortMap.put("5",5);
unsortMap.put("1003",4); Map<String,
Integer> result =unsortMap.entrySet().stream() .sorted(Map.Entry.comparingByKey(Comparator.comparingInt(String::length)) )
.sorted(Map.Entry.comparingByValue()) .collect(Collectors.toMap
(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));
System.println(result);
If you already have a map, and you want to order it, by lenght key, and by value then:
Map<String,Integer> unsortMap = new TreeMap<String,Integer>();
unsortMap.put("103",4);
unsortMap.put("4",4);
unsortMap.put("11101",4);
unsortMap.put("5",5);
unsortMap.put("1003",4);
Map<String, Integer> result = unsortMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey(Comparator.comparingInt(String::length))
).sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(oldValue, newValue) -> oldValue, LinkedHashMap::new));
System.out.println(result);
out => {4=4, 103=4, 1003=4, 11101=4, 5=5}
You can compare in this way, if you are aiming to sort with length.
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
#Override
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
// TODO Auto-generated method stub
if (o1.getKey().length() == o2.getKey().length()) {
return 0;
}
if (o1.getKey().length() > o2.getKey().length()) {
return 1;
} else {
return -1;
}
}
});
You haven't said why it doesn't work. What happens when you try?
In Java, a Map is not meant to be sorted, it is meant for fast access.
However, implementations like TreeMap maintain a deterministic ordering, and can be sorted. Your code suggests you are trying to sort the Map's EntrySet. A Java Set also has no order, and cannot be sorted.
I suggest you either utilise a NavigableMap like a TreeMap, or work with a List rather than a Set. A Java List has an order, and can be sorted using Collections.sort(...).
There are a number of reasons why your existing code may not be working. For one thing, you are attempting to call compareToon an int (at o1.getKey().length().compareTo(...)). This is not possible. You could instead use:
Ints.compare(o1.getKey().length(), o2.getKey().length());
I have this code to populate a HashMap and pass it to TreeMap to sort it in natural order in Key values.
Map<Integer, String[]> hashMap = new HashMap<Integer, String[]>();
hashMap.put(3, new String[]{"1","2"});
hashMap.put(2, new String[]{"1","2"});
hashMap.put(4, new String[]{"1","2"});
hashMap.put(1, new String[]{"1","2"});
System.out.println(hashMap);
Map<Integer, String[]> treeMap = new TreeMap<Integer, String[]>(hashMap);
System.out.println(hashMap); // Natural Order, Ascending
Now my problems is, How can I sort my treeMap in Descending order? I've prepared my Comparator class named KeyComparator that sort Key to descending order. Here is my code below:
public class KeyComparator implements Comparator<Integer> {
#Override
public int compare(Integer o1, Integer o2) {
if (o1 < o2) {
return 1;
} else if (o1 > o2) {
return -1;
} else {
return 0;
}
}
}
The TreeMap has no 2 parameterized Constructor like for example TreeMap(new KeyComparator(),hashMap). How can I use my KeyComparator class at the same time use to load the hashMap into my treeMap.
Do it like below, Create a treemap with Comparator and then use putAll method to pass the hashmap to it.
Map<Integer, String[]> treeMap = new TreeMap<Integer, String[]>(new Comparator<Integer>() {
#Override
public int compare(Integer o1, Integer o2) {
if (o1 < o2) {
return 1;
} else if (o1 > o2) {
return -1;
} else {
return 0;
}
}
});
treeMap.putAll(hashMap);
System.out.println(treeMap); // Descending
Note:- In the example above, I have passed an anonymous implementation of Comparator. You can always pass the KeyComparator in the constructor.
While this may not answer your question, why not simply use an Array to sort it?
System.out.println("map: " + hashMap);
List<Integer> keys = new ArrayList<Integer>(hashMap.keySet());
System.out.println("unsorted keys: " + keys);
Collections.sort(keys);
System.out.println("sorted (asc) keys: " + keys);
Collections.reverse(keys);
System.out.println("sorted (desc) keys: " + keys);
As already mentioned, you need first to use the constructor with the key comparator which is TreeMap(Comparator<? super K> comparator) then use putAll(Map<? extends K,? extends V> m) to add all entries.
If you use Java 8 no need to reimplement it, simply use Comparator.reverseOrder() to get the reverse of the natural ordering as next:
Map<Integer, String[]> treeMap = new TreeMap<>(Comparator.reverseOrder());
treeMap.putAll(hashMap);
System.out.println(treeMap);
The TreeMap has a nice method for this. You can do
TreeMap<Integer, String[]> map = new TreeMap<>(hashMap);
NavigleMap<Integer, String[]> reversedMap = map.descendingMap();
Create an empty TreeMap with your comparator first
Map<Integer, String[]> treeMap = new TreeMap<Integer, String[]>(new KeyComparator());
treeMap.putAll(hashMap);
http://docs.oracle.com/javase/7/docs/api/java/util/TreeMap.html#TreeMap(java.util.Comparator)
You may have to tweak the syntax
Im using the following code to create a hashmap and then sort the values in the hashmap by using a treemap and a comparator. However, the output is rather unexpected.
So any thoughts as to what Im doing wrong would be helpful
Code
public static void main(String[] args) {
System.out.println("Most freq"+mostFreq(" i me hello hello hello me"));
}
public static String[] mostFreq(String str){
if ((str==null)||( str.trim().equalsIgnoreCase("")))
return null;
String[] arr = new String[10];
String[] words= str.split(" ");
Map <String,Integer> map = new HashMap<String,Integer>();
for (String word :words)
{
int count =0;
if (map.containsKey(word))
{
count= map.get(word);
map.put(word, count+1);
}
else
map.put(word, 1);
}
MyComparator comp= new MyComparator(map);
Map<String,Integer> newMap= new TreeMap(comp);
newMap.putAll(map);
Iterator it= newMap.entrySet().iterator();
while (it.hasNext())
{
Map.Entry pairs = (Map.Entry) it.next();
System.out.println("Key "+pairs.getKey()+"-- value"+pairs.getValue());
}
return arr;
}
Here's the comparator
package samplecodes;
import java.util.Comparator;
import java.util.Map;
public class MyComparator implements Comparator {
Map map;
public MyComparator(Map map){
this.map=map;
}
#Override
public int compare(Object o1, Object o2) {
return ((Integer)map.get(o1) >(Integer)map.get(o2)? (Integer)map.get(o1):(Integer)map.get(o2));
}
}
And the output is of the form
me-2
hello-3
i-3
Please check the JavaDoc of compare: You do not return the bigger value, but -1 for o1 < o2, 0 for o1 = o2 and 1 for o1 > o2. So you could write:
#Override
public int compare(Object o1, Object o2) {
return ((Integer) map.get(o1)).compareTo((Integer) map.get(o2);
}
The Java Doc of TreeMap clearly states that:
A Red-Black tree based NavigableMap implementation. The map is sorted
according to the natural ordering of its keys
we should not violate this rule by using TreeMap to sort by values.
However to sort by values, we can do the following:
Create a LinkedList of entries of the map
using Collection.sort to sort the entries
Inserting the sorted entries to a LinkedHashMap: keeps the keys in the order they are inserted, which is currently sorted on natural ordering.
Return the LinkedHashMap as the sorted map.
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());
}
});
Map<K,V> sortedMap = new LinkedHashMap<K,V>();
for(Map.Entry<K,V> entry: entries){
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
}
Reference: Sorting Map by value
What you are doing is really a misuse of tools.
I believe what you need to do is:
Have a list/array of input words (still fine that you get it by splitting the input string)
Create a Map to store the word as key, and frequency as value
Have a collection of unique words, then sort the collection base on the the frequency
When you are doing the output, traverse the sorted unique word list, for each element, get the frequency from the frequencyMap, and output the word + frequency.
Of course you can still make use of something like a TreeSet and use frequency as key, but you should have list of words as the value of this map (aka Multi-Map), instead of writing a problematic comparator which do not follow the contract of Comparator: http://docs.oracle.com/javase/6/docs/api/java/util/Comparator.html#compare%28T,%20T%29 Both your original implementation and the one in comment of one of the answers does not comply with the rule of sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y (The original one is even worse).
some code snippet just for giving you hints:
List<String> words = ....;
Map<String, Integer> wordFrequencyMap = new HashMap<String, Integer>();
// iterate words and update wordFrequencyMap accordingly
List<String> uniqueWords = new ArrayList<String>(new HashSet<String>(words));
Collections.sort(uniqueWords, new WordFrequencyComparator<String>(wordFrequencyMap));
for (String w : uniqueWords) {
System.out.println("word : " + w + " frequency : " + wordFrequencyMap.get(w));
}
The missing part shouldn't be anything difficult.
I have a Map that has strings for both keys and values.
The data is like the following:
"question1", "1"
"question9", "1"
"question2", "4"
"question5", "2"
I want to sort the map based on its keys. So, in the end, I will have question1, question2, question3, and so on.
Eventually, I am trying to get two strings out of this Map:
First String: Questions (in order 1 .. 10)
Second String: Answers (in the same order as the question)
Right now I have the following:
Iterator it = paramMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry) it.next();
questionAnswers += pairs.getKey() + ",";
}
This gets me the questions in a string, but they are not in order.
Short answer
Use a TreeMap. This is precisely what it's for.
If this map is passed to you and you cannot determine the type, then you can do the following:
SortedSet<String> keys = new TreeSet<>(map.keySet());
for (String key : keys) {
String value = map.get(key);
// do something
}
This will iterate across the map in natural order of the keys.
Longer answer
Technically, you can use anything that implements SortedMap, but except for rare cases this amounts to TreeMap, just as using a Map implementation typically amounts to HashMap.
For cases where your keys are a complex type that doesn't implement Comparable or you don't want to use the natural order then TreeMap and TreeSet have additional constructors that let you pass in a Comparator:
// placed inline for the demonstration, but doesn't have to be a lambda expression
Comparator<Foo> comparator = (Foo o1, Foo o2) -> {
...
}
SortedSet<Foo> keys = new TreeSet<>(comparator);
keys.addAll(map.keySet());
Remember when using a TreeMap or TreeSet that it will have different performance characteristics than HashMap or HashSet. Roughly speaking operations that find or insert an element will go from O(1) to O(Log(N)).
In a HashMap, moving from 1000 items to 10,000 doesn't really affect your time to lookup an element, but for a TreeMap the lookup time will be about 1.3 times slower (assuming Log2). Moving from 1000 to 100,000 will be about 1.6 times slower for every element lookup.
Assuming TreeMap is not good for you (and assuming you can't use generics):
List sortedKeys=new ArrayList(yourMap.keySet());
Collections.sort(sortedKeys);
// Do what you need with sortedKeys.
Using the TreeMap you can sort the map.
Map<String, String> map = new HashMap<>();
Map<String, String> treeMap = new TreeMap<>(map);
for (String str : treeMap.keySet()) {
System.out.println(str);
}
Just use TreeMap:
new TreeMap<String, String>(unsortMap);
Be aware that the TreeMap is sorted according to the natural ordering of its 'keys'.
Use a TreeMap!
If you already have a map and would like to sort it on keys, simply use:
Map<String, String> treeMap = new TreeMap<String, String>(yourMap);
A complete working example:
import java.util.HashMap;
import java.util.Set;
import java.util.Map;
import java.util.TreeMap;
import java.util.Iterator;
class SortOnKey {
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<String, String>();
hm.put("3", "three");
hm.put("1", "one");
hm.put("4", "four");
hm.put("2", "two");
printMap(hm);
Map<String, String> treeMap = new TreeMap<String, String>(hm);
printMap(treeMap);
} // main
public static void printMap(Map<String, String> map) {
Set s = map.entrySet();
Iterator it = s.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println(key + " => " + value);
} // while
System.out.println("========================");
} // printMap
} // class
Provided you cannot use TreeMap, in Java 8 we can make use of the toMap() method in Collectors which takes the following parameters:
keymapper: mapping function to produce keys
valuemapper: mapping function to produce values
mergeFunction: a merge function, used to resolve collisions between values associated with the same key
mapSupplier: a function which returns a new, empty Map into which the
results will be inserted.
Java 8 Example
Map<String, String> sample = new HashMap<>(); // Push some values to map
Map<String, String> newMapSortedByKey = sample.entrySet().stream()
.sorted(Map.Entry.<String, String>comparingByKey().reversed())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
Map<String, String> newMapSortedByValue = sample.entrySet().stream()
.sorted(Map.Entry.<String, String>comparingByValue().reversed())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
We can modify the example to use custom comparator and to sort based on keys as:
Map<String, String> newMapSortedByKey = sample.entrySet().stream()
.sorted((e1, e2) -> e1.getKey().compareTo(e2.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
Using Java 8:
Map<String, Integer> sortedMap = unsortMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(oldValue, newValue) -> oldValue, LinkedHashMap::new));
In Java 8
To sort a Map<K, V> by key, putting keys into a List<K>:
List<K> result = map.keySet().stream().sorted().collect(Collectors.toList());
To sort a Map<K, V> by key, putting entries into a List<Map.Entry<K, V>>:
List<Map.Entry<K, V>> result =
map.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toList());
Last but not least: to sort strings in a locale-sensitive manner - use a Collator (comparator) class:
Collator collator = Collator.getInstance(Locale.US);
collator.setStrength(Collator.PRIMARY); // case insensitive collator
List<Map.Entry<String, String>> result =
map.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey(collator))
.collect(Collectors.toList());
This code can sort a key-value map in both orders, i.e., ascending and descending.
<K, V extends Comparable<V>> Map<K, V> sortByValues
(final Map<K, V> map, int ascending)
{
Comparator<K> valueComparator = new Comparator<K>() {
private int ascending;
public int compare(K k1, K k2) {
int compare = map.get(k2).compareTo(map.get(k1));
if (compare == 0)
return 1;
else
return ascending*compare;
}
public Comparator<K> setParam(int ascending)
{
this.ascending = ascending;
return this;
}
}.setParam(ascending);
Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
As an example:
Map<Integer, Double> recommWarrVals = new HashMap<Integer, Double>();
recommWarrVals = sortByValues(recommWarrVals, 1); // Ascending order
recommWarrVals = sortByValues(recommWarrVals, -1); // Descending order
List<String> list = new ArrayList<String>();
Map<String, String> map = new HashMap<String, String>();
for (String str : map.keySet()) {
list.add(str);
}
Collections.sort(list);
for (String str : list) {
System.out.println(str);
}
Just in case you don't want to use a TreeMap:
public static Map<Integer, Integer> sortByKey(Map<Integer, Integer> map) {
List<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet());
list.sort(Comparator.comparingInt(Map.Entry::getKey));
Map<Integer, Integer> sortedMap = new LinkedHashMap<>();
list.forEach(e -> sortedMap.put(e.getKey(), e.getValue()));
return sortedMap;
}
Also, in case you wanted to sort your map on the basis of values, just change Map.Entry::getKey to Map.Entry::getValue.
In Java 8 you can also use .stream().sorted():
myMap.keySet().stream().sorted().forEach(key -> {
String value = myMap.get(key);
System.out.println("key: " + key);
System.out.println("value: " + value);
}
);
A good solution is provided here. We have a HashMap that stores values in unspecified order. We define an auxiliary TreeMap and we copy all data from HashMap into TreeMap using the putAll method. The resulting entries in the TreeMap are in the key-order.
Use the below tree map:
Map<String, String> sortedMap = new TreeMap<>(Comparator.comparingInt(String::length)
.thenComparing(Function.identity()));
Whatever you put in this sortedMap, it will be sorted automatically. First of all, TreeMap is sorted implementation of Map Interface.
There is a but as it sorts keys in the natural order fashion. As the Java documentation says, String type is a lexicographic natural order type. Imagine the below list of numbers with the String type. It means the below list will not be sorted as expected.
List<String> notSortedList = List.of("78","0", "24", "39", "4","53","32");
If you just use the default TreeMap constructor like below and push each element one-by-one like below:
Map<String, String> map = new TreeMap<>();
for (String s : notSortedList) {
map.put(s, s);
}
System.out.println(map);
The output is: {0=0, 14=14, 24=24, 32=32, 39=39, 4=4, 48=48, 53=53, 54=54, 78=78}
As you see, number 4, for example, comes after '39'. This is the nature of the lexicographic data types, like String. If that one was an Integer data type then that was okay though.
To fix this, use an argument to first check the length of the String and then compare them. In Java 8 it is done like this:
Map<String, String> sortedMap = new TreeMap<>(Comparator.comparingInt(String::length)
.thenComparing(Function.identity()));
It first compares each element by length then apply check by compareTo as the input the same as the element to compare with.
If you prefer to use a more understandable method, the above code will be equivalent with the below code:
Map<String, String> sortedMap = new TreeMap<>(
new Comparator() {
#Override
public int compare(String o1, String o2) {
int lengthDifference = o1.length() - o2.length();
if (lengthDifference != 0)
return lengthDifference;
return o1.compareTo(o2);
}
}
);
Because the TreeMap constructor accepts the comparator Interface, you can build up any an even more complex implementation of Composite classes.
This is also another form for a simpler version.
Map<String,String> sortedMap = new TreeMap<>(
(Comparator<String>) (o1, o2) ->
{
int lengthDifference = o1.length() - o2.length();
if (lengthDifference != 0)
return lengthDifference;
return o1.compareTo(o2);
}
);
Use LinkedHashMap, which provides the key ordering. It's also gives the same performance as HashMap. They both implement the Map interface, so you can just replace the initialization object HashMap to LinkedHashMap.
We can also sort the key by using Arrays.sort method.
Map<String, String> map = new HashMap<String, String>();
Object[] objArr = new Object[map.size()];
for (int i = 0; i < map.size(); i++) {
objArr[i] = map.get(i);
}
Arrays.sort(objArr);
for (Object str : objArr) {
System.out.println(str);
}