When I need to sort a HashMap by value, the advice seems to be to create the HashMap and then put the data into a TreeMap which is sorted by value.
For example: Sort a Map<Key, Value> by values (Java)
My question: why is it necessary to do this? Why not create a TreeMap(which is sorted by keys) and then sort it in place by value?
If you know your values to be unique, you can use Guava's BiMap (bidirectional map) to store the data. Create a HashBiMap as you would your HashMap, then create a new TreeMap from its inverse:
new TreeMap<>(biMap.inverse());
That map will then be sorted by the values. Remember that what you're thinking of as "keys" and "values" will be swapped.
If your values are not unique, you can create a multimap of the inverse. A multimap is essentially a mapping from each key to one or more values. It's usually implemented by making a map from a key to a list. You don't have to do that though, because Google did it for you. Just create a multimap from your existing map, and ask Guava to invert it for you into a TreeMultimap, which, as you can guess, is a TreeMap that can hold multiple values per key.
Multimaps.invertFrom(Multimaps.forMap(myMap), new TreeMultimap<V, K>());
Multimap documentation is provided.
Because you can't reorder the entries of a TreeMap manually. TreeMap entries are always sorted on the keys.
I'm going to throw out Map that could be iterated in the order of values as another answer to "How to do it," though...specifically, a solution which doesn't return a map that chokes (by throwing exceptions) on queries to keys not in your original map.
I have this very small code which is working fine:
public class SortMapByValues {
public static void main(String[] args) {
Map<Integer, String> myMap = new LinkedHashMap<Integer, String>();
myMap.put(100, "hundread");
myMap.put(500, "fivehundread");
myMap.put(250, "twofifty");
myMap.put(300, "threehundread");
myMap.put(350, "threefifty");
myMap.put(400, "fourhundread");
myMap = sortMapByValues(myMap);
for (Map.Entry<Integer, String> entry : myMap.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
public static Map<Integer, String> sortMapByValues(
Map<Integer, String> firstMap) {
Map<String, Integer> SecondyMap = new TreeMap<String, Integer>();
for (Map.Entry<Integer, String> entry : firstMap.entrySet()) {
SecondyMap.put(entry.getValue(), entry.getKey());
}
firstMap.clear();
for (Map.Entry<String, Integer> entry : SecondyMap.entrySet()) {
firstMap.put(entry.getValue(), entry.getKey());
}
return firstMap;
}
}
Output:
500 fivehundread
400 fourhundread
100 hundread
350 threefifty
300 threehundread
250 twofifty
I wrote the following one-liner using Java 8 Stream API to sort any given map by value:
List<Map.Entry<String, String>> sortedEntries = map.entrySet().stream()
.sorted((o1, o2) -> o1.getValue().compareTo(o2.getValue())).collect(Collectors.toList());
Related
I came across a problem of sorting a HashMap<String, Integer> based on values. But, I came across many articles over the internet which first created a linkedList/arraylist of Map.Entry<String, Integer> and then sorted it on the basis of value.
Below is the code snippet showing sorting of a hashmap on the basis of key.
// Java program to sort hashmap by values
import java.util.*;
import java.lang.*;
public class Main {
// function to sort hashmap by values
public static HashMap<String, Integer> sortByValue(HashMap<String, Integer> hm)
{
// Create a list from elements of HashMap
List<Map.Entry<String, Integer> > list =
new LinkedList<Map.Entry<String, Integer> >(hm.entrySet());
// Sort the list
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());
}
});
// put data from sorted list to hashmap
HashMap<String, Integer> temp = new LinkedHashMap<String, Integer>();
for (Map.Entry<String, Integer> aa : list) {
temp.put(aa.getKey(), aa.getValue());
}
return temp;
}
// Driver Code
public static void main(String[] args)
{
HashMap<String, Integer> hm = new HashMap<String, Integer>();
// enter data into hashmap
hm.put("Math", 98);
hm.put("Data Structure", 85);
hm.put("Database", 91);
hm.put("Java", 95);
hm.put("Operating System", 79);
hm.put("Networking", 80);
Map<String, Integer> hm1 = sortByValue(hm);
// print the sorted hashmap
for (Map.Entry<String, Integer> en : hm1.entrySet()) {
System.out.println("Key = " + en.getKey() +
", Value = " + en.getValue());
}
}
}
My question is, why is there a need to convert hashmap to list of entrySet and then sort it?
According to my understanding, we should be able to directly sort it based on the values just like any POJO class on a certain parameter. There shouldn't be any need to convert it into some collection and then sort it.
Instead of placing the values in a map and sorting, create a record or class to hold the data. After populating with instances of the class, sort the list prior to placing in the map. It is necessary to use a LinkedHashMap to preserve the sorted order. Otherwise, the normal behavior of a HashMap will most likely disturb the sort.
record Rec(String getName, int getVal) {}
List<Rec> list = List.of(
new Rec("Math", 98), new Rec("Data Structure", 85),
new Rec("Database", 91), new Rec("Java", 95),
new Rec("Operating System", 79), new Rec("Networking", 80));
Map<String, Integer> result = list.stream()
.sorted(Comparator.comparing(Rec::getVal))
.collect(Collectors.toMap(Rec::getName, Rec::getVal,
(a, b) -> a, LinkedHashMap::new));
result.entrySet().forEach(System.out::println);
prints
Operating System=79
Networking=80
Data Structure=85
Database=91
Java=95
Math=98
The issue, imo, is that not just any map lends itself to be sorted on values. Sorting a regular HashMap after the fact on values would be fruitless since inserting the keys would still perturb the order. Sorting before wouldn't help. A TreeMap won't work since it is designed to sort on keys and any supplied Comparator sorts using a KeyExtractor. And I suspect that any Map that might allow this would still convert to another data structure to accomplish the sort and return some LinkedHashMap or equivalent.
I have two HashMap in Java.
First one contains a key and its value. Where second contains an evaluation index (order) of that keys. I want to sort the first map by referring to the second map.
First HashMap <key, value>
<"C","ccc">
<"D","ddd">
<"A","aaa">
<"B","bbb">
Second HashMap <key, value>
<"0","A">
<"1","B">
<"2","C">
<"3","D">
Result should be
<"A","aaa">
<"B","bbb">
<"C","ccc">
<"D","ddd">
Looping this two map and checking comparing keys is simple but not efficient. Any efficient idea?
You can use Java Stream API. First, sort the second map entrySet by key then map second map's value as key and get first map's value by value of the second map and then collect as LinkedHashMap using Collectors.toMap
secondMap.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.collect(Collectors.toMap(Map.Entry::getValue, k -> firstMap.get(k.getValue()),
(x, y) -> y, LinkedHashMap::new));
If you use LinkedHashMap/TreeMap ordered by key for the second map then you don't need to sort the keys. See demo here
First of all HashMap is not ordered collections. The key-value pairs in HashMap are ordered based on the value of hashCode() result of keys. So I would say you can't keep sorted values in HashMap.
Instead, you can use LinkedHashMap - it will be ordered with order of insertion.
And for your solution, i would do:
HashMap<String, String> firstMap = ...
HashMap<String, String> secondMap = ...
LinkedHashMap<String, String> orderedMap = new LinkedHashMap<>();
for (int i = 0; i < secondMap.size(); ++i) {
String key = secondMap.get(String.valueOf(i));
orderedMap.put(key, firstMap.get(key));
}
Did not run this code, but it should work.
Alternatively, you can use TreeMap that is ordered based on Comparable interface of the keys.
And to answer what is better to use - TreeMap or LinkedHashMap - depends on how actually you are using this map later. In most cases LinkedHashMap is enough, although if you need to, for example, get the closest greater element to some key, then TreeMap is a choice.
There are some comparison between HashMap and TreeMap
What is the difference between a HashMap and a TreeMap?
Traverse the values of secondMap and collect the related key, value set in the map:
Map<String, String> result = secondMap.values().stream()
.collect(Collectors.toMap(Function.identity(), firstMap::get, (x,y)-> x, LinkedHashMap::new));
Try this:
Map<String, String> firstMap = new HashMap<>();
firstMap.put("C", "ccc");
firstMap.put("D", "ddd");
firstMap.put("A", "aaa");
firstMap.put("B", "bbb");
Map<String, String> secondMap = new HashMap<>();
secondMap.put("0", "A");
secondMap.put("1", "B");
secondMap.put("2", "C");
secondMap.put("3", "D");
Map<String, String> result = secondMap.values().stream()
.collect(Collectors.toMap(Function.identity(), firstMap::get, (x,y)-> x, LinkedHashMap::new));
System.out.println(result);
Say I have mappings from Strings to a Mapping from Strings to int, such as
Map<String, Map<String, Integer>> myMap1 = new HashMap<>();
myMap1.put("A", Map.of("X", 1))
myMap1.put("B", Map.of("Y", 1))
Map<String, Map<String, Integer>> myMap2 = new HashMap<>();
myMap2.put("B", Map.of("Y", 3))
I would like to merge these mappings such that we get a mapping where the key is the inner map's key, and the value would be the average of the inner maps values of the same keys.
So the output to the example above would be
{"X" : 1, "Y", 2}
We can discard the outer map's key altogether.
What is the nicest way to do this with java. I thought there might be some nice way to do it with Collectors.groupBy method but I am quite inexperienced with this.
I’m going to assume there might be more than two maps, so let’s make a List out of them:
Collection<Map<String, Map<String, Integer>>> myMaps =
List.of(myMap1, myMap2);
Then we can use flatMap on the values() of each Map, which gives us a stream of Map<String, Integer> maps.
We can obtain the entrySet() of each of those, then apply flatMap to the streams of those entry sets, to give us a single Stream of Map.Entry<String, Integer> objects, which we can then group.
There is a groupingBy method which takes a second Collector for customizing the values of the groups, by collecting all of the grouped values seen. We can use that to get our averages, using an averaging collector.
Map<String, Double> averages =
myMaps.stream().flatMap(map -> map.values().stream()) // stream of Map<String, Integer>
.flatMap(innerMap -> innerMap.entrySet().stream()) // stream of Map.Entry<String, Integer>
.collect(Collectors.groupingBy(Map.Entry::getKey, // group by String key
Collectors.averagingInt(Map.Entry::getValue))); // value for each key = average of its Integers
I have created a map called result.
In the sortByKeys method as my keys are String with Numeric values, I have converted them to Integer key type Map then sorted them.
The sorting is working fine when I am looping and printing individually, but not when I am setting them in another Map.
public class TestDate {
public static void main (String args[]){
Map<String, String> result = new HashMap<String, String>();
result.put("error", "10");
result.put("1","hii");
result.put("Update","herii");
result.put("insert","insert");
result.put("10","hiiuu");
result.put("7","hii");
result.put("21","hii");
result.put("15","hii");
Map<String, String> sorted = sortByKeys(result);
//System.out.println(sorted);
}
private static Map<String, String> sortByKeys(Map<String, String> map) {
Map <Integer,String> unSorted = new HashMap<Integer, String>();
Map <String,String> sorted = new HashMap<String, String>();
for (Map.Entry<String, String> entry : map.entrySet())
{
try{
int foo = Integer.parseInt(entry.getKey());
unSorted.put(foo, entry.getValue());
}catch (Exception e){
}
}
Map<Integer, String> newMap = new TreeMap<Integer, String>(unSorted);
Set set = newMap.entrySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
Map.Entry me = (Map.Entry)iterator.next();
System.out.println(me.getKey());
System.out.println(me.getValue());
sorted.put(me.getKey().toString(), me.getValue().toString());
}
System.out.println(sorted);
return null;
}
}
Here is the o/p :
1
hii
7
hii
10
hiiuu
15
hii
21
hii
{21=hii, 10=hiiuu, 1=hii, 7=hii, 15=hii}
If you don't need the last inch of performance, you can solve this rather directly, without an extra step to sort the map, by using SortedMap:
Map<String,String> result = new TreeMap<>(Comparator.comparingInt(Integer::parseInt));
If you are among the unfortunate bunch who are still being denied access to Java 8, you'll have to implement the Comparator in long-hand:
new TreeMap<>(new Comparator<String,String> { public int compare(String a, String b) {
return Integer.compare(Integer.parseInt(a), Integer.parseInt(b));
}});
The above approach works only under the assumption that all keys are parseable integers. If that is not the case, then you won't be able to use the SortedMap directly, but transform your original map into it, filtering out the unparseable keys.
It's because the Map you're putting them into is a HashMap, which isn't sorted. There's no guarantee of the ordering of results you'll get out of the HashMap, even if you put them in in the right order.
(And calling it sorted doesn't change anything :) )
You print 2 different maps and not the same: you iterate over and print the entries of newMap map, and at the end you print sorted map.
You see the sorted entries printed because you iterate over your sorted newMap.
Then you print the sorted map which is unsorted (despite by its name). You print a different map instance.
Print this:
System.out.println(newMap); // This is the instance of the sorted "TreeMap"
This question already has answers here:
Partial search in HashMap
(5 answers)
Closed 6 years ago.
I am currently using HashMap<String, Integer> which is filled with keys of type String which are all, let's say, 5 chars long. How can I search for an specific key of 4 chars or less, which is part and at the beginning of some other keys and get all hits as a collection of <Key, Value>?
Iterate is your only option unless you create a custom data structure:
for (Entry<String, Integer> e : map.entrySet()) {
if (e.getKey().startsWith("xxxx")) {
//add to my result list
}
}
If you need something more time efficient then you'd need an implementation of map where you are tracking these partial keys.
It seems like a use case for TreeMap rather than HashMap. The difference is that TreeMap preserves order. So you can find your partial match much quicker. You don't have to go through the whole map.
Check this question Partial search in HashMap
You cannot do this via HashMap, you should write your own implementation for Map for implementing string length based searching in a map.
Map<String, Integer> result = new HashMap<String, Integer>;
for(String key : yourMap.keySet()) {
if(key.length() == 4){
result.put(key, yourMap.get(key);
}
}
After executing this code you have all key/value pairs with 4 letter keys in result.
Set<Entry<String, Integer>> s1 = map.entrySet();
for (Entry<String, Integer> entry : s1) {
if(entry.getKey().length == 4)
//add it to a map;
}
First get the entry set to your hashmap. Iterate through the set and check the length of each key and add it to a map or use it as u want it.
With HashMap<String, Integer> you can only go through keySet() and do contains() for String keys and your pattern.
As has been noted, there isn't a terribly efficient* way to do it with the datastructure you have specified. However, if you add an additional Map<Integer, List<String>> to keep track of the mapping from string length to the list of all keys with that length, then you will be able to do this very efficiently.
*Using just the Map<String, Integer>, you would need to iterate through the entire capacity of the larger map, whereas adding this supplemental datastructure would impose an O(1) lookup (assuming you used a HashMap) followed by iteration through just the result set, which is the fastest possible outcome.
You can try this approach:
public Map<String,Integer> filterMap(Map<String, Integer> inputMap){
Map<String, Integer> resultHashMap = new HashMap<String, Integer>();
for (String key : inputMap.keySet()) {
if(key.length()==5){
resultHashMap.put(key,inputMap.get(key));
}
}
return resultHashMap;
}