Java sort a Hashmap on Value - java

Learning Java as I go (Python background). Simple word count program in Java 7 code (can not use J8!).
I have a hash map of word:count pairs. Now I need to sort on count (decreasing order), and break ties with using word in alphabetical order.
Have read s/o and I tried a treemap but it does not seem to handle ties very well so I don't think that is right.
I have seen a lot of solutions posted that define a new class sortbyvalue and define a comparator. These will not work for me as I need to keep the solution all contained in the existing class.
I am looking for feedback on this idea:
iterate over the map entries (me) in the hashmap
use me.getKey = K and me.getValue = V
new Map.Entry reverse_me = (V,K) {not sure about this syntax}
add reverse_me to a List
repeat for all me in map
List.sort { this is where I am unsure, on how this will sort and no idea how to write a comparator. At this point each List element would be a (count, word) pair and the sort() should sort by count in decreasing and then by word in case of same counts in alphabetical order)
This would be the final output.
Is this a logical progression? I can tell from the many posts that there are many opinions on how to do this, but this is the one I can wrap my head around.
Also, can not use Guava.

You can create an List of Entry set from the map. Sort the List using Collections.sort(). You can pass the custom Comparator for sorting by Key when Value(s) are same.
Set<Entry<String, Integer>> set = map.entrySet();
List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(set);
Collections.sort( list, new Comparator<Map.Entry<String, Integer>>()
{
public int compare( Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2 )
{
int result = (o2.getValue()).compareTo( o1.getValue() );
if (result != 0) {
return result;
} else {
return o1.getKey().compareTo(o2.getKey());
}
}
} );

This collection reflects the correct order only as long as the map entries are not changed
HashMap<String, Integer> map = new HashMap<>();
TreeSet<Map.Entry<String, Integer>> entriesSet = new TreeSet<>(new Comparator<Map.Entry<String, Integer>>(){
#Override
public int compare(Map.Entry<String, Integer> me1, Map.Entry<String, Integer> me2) {
return me1.getValue().compareTo(me2.getValue());
}
});
entriesSet.addAll(map.entrySet());

Related

Why sorting a HashMap<T, T> requires converting and then sorting a List<Map.Entry<T, T>>?

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.

Losing data when transferring data from TreeMap to TreeSet.

I have a TreeMap like so.
// Create a map of word and their counts.
// Put them in TreeMap so that they are naturally sorted by the words
Map<String, Integer> wordCount = new TreeMap<String, Integer>();
wordCount.put("but", 100);
wordCount.put("all", 10);
Since it is a TreeMap the content are sorted by key, i.e. the words.
// Iterate over the map to confirm that the data is stored sorted by words.
// This part is also working nicely and I can see that the ouput is sorted by
// words.
Set<String> words = wordCount.keySet();
logger.debug("word, count");
for (Iterator<String> itForWords = words.iterator(); itForWords.hasNext();) {
String word = (String) itForWords.next();
Integer count = wordCount.get(word);
logger.debug("{}, {}", word, count);
}
Now I am trying to sort them by count. Since TreeMap will not drop the trick I am moving them to a SortedSet.
// Trying to sort the collection by the count now.
// TreeMap cant be sorted on values.
// Lets put them in a sorted set and put a comparator to sort based on values
// rather than keys.
SortedSet<Map.Entry<String, Integer>> wordCountSortedByCount = new TreeSet<Map.Entry<String, Integer>>(
new Comparator<Map.Entry<String, Integer>>() {
#Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue().compareTo(o1.getValue());
}
});
wordCountSortedByCount.addAll(wordCount.entrySet());
At this point I am expecting the TreeSet to have 2 entries. But it is showing only one. Please help.
// This is NOT WORKING
// The size is only 1. It should have been two.
logger.debug("Size of sorted collection is {}", wordCountSortedByCount.size());
To avoid such errors, it worth to use comparator in Java 8:
Comparator.comparing(Map.Entry::getValue)
SortedSet<Map.Entry<String, Integer>> wordCountSortedByCount =
new TreeSet<>(Comparator.comparing(Map.Entry::getValue));
Modify return o1.getValue().compareTo(o1.getValue()); to
return o1.getValue().compareTo(o2.getValue());
Output will be 2.

How do I sort the elements of an HashMap according to their values? [duplicate]

This question already has answers here:
Closed 10 years ago.
I have the following HashMap:
HashMap<String, Integer> counts = new HashMap<String, Integer>();
What is the simplest way to order it according to the values?
You can't sort a Map by the values, especially not a HashMap, which can't be sorted at all.
Instead, you can sort the entries:
List<Map.Entry<String, Integer>> entries = new ArrayList<Map.Entry<String, Integer>>(map.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<String, Integer>>() {
public int compare(
Map.Entry<String, Integer> entry1, Map.Entry<String, Integer> entry2) {
return entry1.getValue().compareTo(entry2.getValue());
}
});
will sort the entries in ascending order of count.
You can get a set of entries (Set of Map.Entry) from a map, by using map.entrySet(). Just iterate over them, and check the values by getValue().
A work around, if you want to them print them in order(Not storing).
Create a new Map (tempMap) and put your value as key and key as value. To make the keys unique, please add some unique value in each of the keys e.g. key1 = value1+#0.
Get the list of values as map.values() as list myVlues
Sort the myVlues list as Collections.sort(myVlues)
Now iterate the myVlues, get the corresponding key from tempMap, restore the key e.g. key.substring(0, key.length-2) and print the key and value pair.
Hope this helps.
A TreeMap can keep its entries in an order defined by a Comparator.
We can create a comparator that will order the Map by putting the greatest value first.
Then, we will build a TreeMap that uses that Comparator.
We will then put all the entries in our counts map into the Comparator.
Finally, we will get the first key in the map, which should be the most common word (or at least one of them, if multiple words have equal counts).
public class Testing {
public static void main(String[] args) {
HashMap<String,Double> counts = new HashMap<String,Integer>();
// Sample word counts
counts.put("the", 100);
counts.put("pineapple",5);
counts.put("a", 50);
// Step 1: Create a Comparator that order by value with greatest value first
MostCommonValueFirst mostCommonValueFirst = new MostCommonValueFirst(counts);
// Step 2: Build a TreeMap that uses that Comparator
TreeMap<String,Double> sortedMap = new TreeMap<String,Integer (mostCommonValueFirst);
// Step 3: Populate TreeMap with values from the counts map
sortedMap.putAll(counts);
// Step 4: The first key in the map is the most commonly used word
System.out.println("Most common word: " + sortedMap.firstKey());
}
}
private class MostCommonValueFirst implements Comparator<String> {
Map<String, Integer> base;
public MostCommonValueFirst(Map<String, Integer> base) {
this.base = base;
}
// Note: this comparator imposes orderings that are inconsistent with equals.
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return 1;
} else {
return -1;
} // returning 0 would merge keys
}
}
Source: https://stackoverflow.com/a/1283722/284685

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

Implementation of sorting in Vector

I have a Collection as
Vector<HashMap<String, String>>
Actually I am using this as list items of list view in android.I used SortedMap but I did not get correct result. I think this is because HashMap's structure is
hashMap.add("Name","value_of_name");
hashMap.add("Counts","value_of_counts");
Now I am adding it to Vector.
I want to sort vector's element by Name key of hashMap.
I know about Collection.sort and I can sort this using ArrayList and POJO class. But I do not know how to use it with adapter of my ListView
How can I sort elements. And is there any better solution (Regarding my data-structure of collection, which can be use with adapter easily) ?
You need to a implement a Comparator<HashMap<String,String> > and place the logic of your sort ordering inside its compare method.
Not sure I understand correctly. This will sort the vector on one key of the maps.
Collections.sort(yourVector, new Comparator<HashMap<String,String>>() {
public int compare(HashMap<String,String> a, HashMap<String,String> b) {
return a.get(yourKey).compareTo(b.get(yourKey));
}
});
Have you never thought about taking a look at collections in java.util package ?
You would then have discovered that Treemap already implements balanced tree sorting for Comparable items, like String is.
So, to have your items sorted, just repalce your HashMap with a TreeMap, and all the work will be done.
BTW what does this vector does here ? They're sooo Java 1.1 (fifteen years old, in other words)
If you want to sort the maps in the array, use a SortedMap implementation like TreeMap or ConcurrentSkipListMap. This takes a vector of HashMaps and returns a ArrayList (a non-synchronized and faster collection than Vector) of SortedMaps.
public ArrayList<SortedMap<String, String>> sortMaps(Vector<HashMap<String, String> maps) {
ArrayList<TreeMap<String, String>> returnMaps = new ArrayList<TreeMap<String, String>>();
for(HashMap<String, String> theMap : maps) {
// TreeMap is a sorted map and this will use the default String.compareTo
TreeMap<String, String> newMap = new TreeMap<String, String>();
// put all the items from the HashMap into the TreeMap, which will autosort
newMap.putAll(theMap);
returnMaps.add(newMap);
}
return returnMaps;
}
To sort the Vector by the first keys (lowest keys, first alphabetically) of the hash map try the following before the return line:
// this sorts the vector by first keys
Collections.sort(returnMaps, new Comparator<SortedMap<String,String>>() {
public int compare(SortedMap<String,String> a, HashMap<String,String> b) {
return a.firstKey().compareTo(b.firstKey());
}
});
Or if you want to sort by last key (highest keys, last alphabetically):
// this sorts the vector by first keys
Collections.sort(returnMaps, new Comparator<SortedMap<String,String>>() {
public int compare(SortedMap<String,String> a, HashMap<String,String> b) {
return a.lastKey().compareTo(b.lastKey());
}
});
To return one sorted map of all keys (will stomp on any duplicates):
public SortedMap<String, String> singledSortedMap(Vector<HashMap<String, String> maps) {
// this will end up with all the values, sorted by natural string ordering
SortedMap<String, String> returnMap = new TreeMap<String, String>();
for(HashMap<String, String> theMap : maps) {
returnMap.putAll(theMap);
}
return returnMap;
}
The best (fastest) way it to use a TreeMap instead. If you supply it with the correct Comperator all the items in the TreeMap will be sorted.
The important question: Why do you have a Vector of HashMaps?

Categories

Resources