Efficient way to get the most used keys in a HashMap - Java - java

I have a HashMap where the key is a word and the value is a number of occurrences of that string in a text. Now I'd like to reduce this HashMap to only 15 most used words (with greatest numbers of occurrences). Do you have any idea to do this efficiently?

Using an array instead of ArrayList as suggested by Pindatjuh could be better,
public class HashTest {
public static void main(String[] args) {
class hmComp implements Comparator<Map.Entry<String,Integer>> {
public int compare(Entry<String, Integer> o1,
Entry<String, Integer> o2) {
return o2.getValue() - o1.getValue();
}
}
HashMap<String, Integer> hm = new HashMap<String, Integer>();
Random rand = new Random();
for (int i = 0; i < 26; i++) {
hm.put("Word" +i, rand.nextInt(100));
}
ArrayList list = new ArrayList( hm.entrySet() );
Collections.sort(list, new hmComp() );
for ( int i = 0 ; i < 15 ; i++ ) {
System.out.println( list.get(i) );
}
}
}
EDIT reversed sorting order

One way I think of to tackle this, but it's probably not the most efficient, is:
Create an array of hashMap.entrySet().toArray(new Entry[]{}).
Sort this using Arrays.sort, create your own Comparator which will compare only on Entry.getValue() (which casts it to an Integer). Make it order descending, i.e. most/highest first, less/lowest latest.
Iterate over the sorted array and break when you've reached the 15th value.

Map<String, Integer> map = new HashMap<String, Integer>();
// --- Put entries into map here ---
// Get a list of the entries in the map
List<Map.Entry<String, Integer>> list = new Vector<Map.Entry<String, Integer>>(map.entrySet());
// Sort the list using an annonymous inner class implementing Comparator for the compare method
java.util.Collections.sort(list, new Comparator<Map.Entry<String, Integer>>(){
public int compare(Map.Entry<String, Integer> entry, Map.Entry<String, Integer> entry1)
{
// Return 0 for a match, -1 for less than and +1 for more then
return (entry.getValue().equals(entry1.getValue()) ? 0 : (entry.getValue() > entry1.getValue() ? 1 : -1));
}
});
// Clear the map
map.clear();
// Copy back the entries now in order
for (Map.Entry<String, Integer> entry: list)
{
map.put(entry.getKey(), entry.getValue());
}
Use first 15 entries of map. Or modify last 4 lines to put only 15 entries into map

You can use a LinkedHashMap and remove the least recently used items.

Related

Getting two elements from a map which their sum equals to a desired number

I am trying to find a way to get the first 2 elements from a map which their value combined gives me a desired sum.
I was thinking of a solution which combined 2 maps where the key of the second map is the reminder of the target number minus the value of the entry of the first map.
I am lost and not sure what I am missing.
What am I missing here?
I would suggest introducing a new Map remainderToItem, looping through all relevant items and adding their reminder to the Map as key and item key as the value
then iterate the relevantItems Map again to find the price is matching with some other reminder
Also check remainderToItem.get(entry.getValue()).equals(entry.getKey())) (in case the value is 50 reminder is also 50), this prevents adding same item again to itemsThatCanBeBought
private static List<String> getTwoItemsWhichSumTo100(Map<String, Integer> items, int target) {
Map<String, Integer> relevantItems = getRelevantItems(items, target);
Map<Integer, String> remainderToItem = new HashMap<>();
List<String> itemsThatCanBeBought = new ArrayList<>();
for (Map.Entry<String, Integer> entry : relevantItems.entrySet()) {
int remainder = target - entry.getValue();
remainderToItem.put(remainder, entry.getKey());
}
for (Map.Entry<String, Integer> entry : relevantItems.entrySet()) {
if (remainderToItem.containsKey(entry.getValue()) && !remainderToItem.get(entry.getValue()).equals(entry.getKey())) {
itemsThatCanBeBought.add(entry.getKey());
itemsThatCanBeBought.add(remainderToItem.get(entry.getValue()));
return itemsThatCanBeBought;
}
}
return itemsThatCanBeBought;
}
private static Map<String, Integer> getRelevantItems(Map<String, Integer> items, int target) {
Map<String, Integer> relevantItems = new HashMap<>();
for (Map.Entry<String, Integer> entry : items.entrySet()) {
if (entry.getValue() < target) relevantItems.put(entry.getKey(), entry.getValue());
}
return relevantItems;
}
the problem you're facing is that the int is the value , not the key.
so pairOfItems.containsKey(remainder) should not compile.
luckily for you, Map also has containsValue() method. as long as there is no requirement for minimum performance, that should solve your problem.
...and you don't need a 2nd map. you can just ask items.containsValue(reminder)

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.

Java sort a Hashmap on Value

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());

Sort Map by Top Scores

I have a map that stores a players name and there score on which i update and things fine.
public static Map<String, Integer> map = new HashMap<String, Integer>();
After the first round of my game finishes i want to remove the lowest scores from the map. For instance, There could be 8 players in the map 4 of whom have a score of 10 and the other 4 have a score of 0 how would i split the map in half based on the top scorers and remove the bottom 4? Keep in mind the 8 players is not defined, it could be any number
No one seems to have picked up on this not being a Mapping. If you look at high scores, names can be duplicates. What you want is an NavigableSet where
class HighScore implements Comparable<HighScore> {
private static final AtomicLong NEXT_ID = new AtomicLong(1);
protected final String name;
protected final long id = NEXT_ID.getAndIncrement();
protected final int score;
// ...
public int compareTo(HighScore o) {
int diff = score - o.score;
if (diff != 0) { return diff; }
long idDiff = id - o.id;
if (idDiff < 0) { return -1; }
else if (idDiff > 0) { return 1; }
else { return 0; }
}
}
And then you can just pollFirst() to remove.
Below is what might help you. I populated the Map with same values for key and values, to see the order when it is printed. The below example includes
a way of getting the top half view without removing the bottom half from original map
and also removing bottom half from original map.
Unless the requirement is to remove do not need to remove bottom half, still get the headMap that is backed by the original map.
import java.util.NavigableMap;
import java.util.TreeMap;
public class HalfTheMap {
static void addValues(TreeMap<String, Integer> map)
{
map.put("11", 11);
map.put("33", 33);
map.put("77", 77);
map.put("44", 44);
map.put("55", 55);
map.put("22", 22);
//map.put("66", 66);
}
public static void main(String[] args) {
TreeMap<String, Integer> map = new TreeMap<String, Integer>();
addValues(map);
System.out.printf("Original Map Initial Values : %s\n",map);
int size = map.size();
int midIndex = (size/2) - 1;
System.out.printf("size : %d \nmid : %d\n", size, midIndex);
// retrieve key of middle element
String midKey = (String)map.keySet().toArray()[midIndex];
// Top half view of the original map
NavigableMap<String, Integer> topMap = map.headMap(midKey, true);
System.out.printf("Top half map : %s\n", topMap);
// remove the bottom half from original map.
map.tailMap(midKey, false).clear();
System.out.printf("Original map after bottom half removed : %s\n", map);
}
}
Prints :
Original Map Initial Values : {11=11, 22=22, 33=33, 44=44, 55=55, 77=77}
size : 6
mid : 2
Top half map : {11=11, 22=22, 33=33}
Original map after bottom half removed : {11=11, 22=22, 33=33}
I am leaving the fine tuning of halving when size is odd value to you and any other fine tuning that are appropriate to your needs.
NOTE : I have noticed that my example using keys and values of same value in each entry seems to show that it could be a solution, infact not exactly. Though it demonstrates the usage of some important methods to solve the problem.
As answered by David Ehrmann above, changing the collections to Set rather than Map and using a class that modal name and score could be a better solution.
Hope this helps.
But there's a simplier way to do this.
You can use custom sorter algorithm on TreeMap.
For example: TreeMap map = new TreeMap(Your own comparator);
You can write your own comparator implementing the Comparator interface.
Example (taken from StackOverflow, don't know more precisely):
class ValueComparator implements Comparator {
Map<String, Integer> base;
public ValueComparator(Map<String, Integer> base) {
this.base = base;
}
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return -1;
} else {
return 1;
} // returning 0 would merge keys
}
}
Then, you only have to edit this code, to ascending order, then you only have to remove the first X. elements from.
I found this post that might help. One you cannot sort a Hashmap because there is no definitive order to it. If you want to sort through a LinkedHashMap though, this is how you do it. (A LinkedHashMap just has a definitive iterative order)
public LinkedHashMap sortHashMapByValues(HashMap passedMap) {
List mapKeys = new ArrayList(passedMap.keySet());
List mapValues = new ArrayList(passedMap.values());
Collections.sort(mapValues);
Collections.sort(mapKeys);
LinkedHashMap sortedMap = new LinkedHashMap();
Iterator valueIt = mapValues.iterator();
while (valueIt.hasNext()) {
Object val = valueIt.next();
Iterator keyIt = mapKeys.iterator();
while (keyIt.hasNext()) {
Object key = keyIt.next();
String comp1 = passedMap.get(key).toString();
String comp2 = val.toString();
if (comp1.equals(comp2)){
passedMap.remove(key);
mapKeys.remove(key);
sortedMap.put((String)key, (Double)val);
break;
}
}
}
return sortedMap;
}
If you want to remove the lowest value on the other hand, I doubt this is the easiest thing you could do something like this to sort them.
public static Entry<String, Integer> removeLowest(LinkedHashMap<String, Integer> map){
Entry<String, Integer> lowest = null;
for(Entry<String,Integer> e: map){
if(lowest==null || e.getValue().compareTo(lowest.getValue()) < 0){
lowest = e;
}
}
return lowest;
}
PS: Don't forget to accept my answer if it works for you.
Update: If you want to remove say half of the map. You would sort it first then do this.
public static LinkedHashMap<String, Integer> getTopHalf(LinkedHashMap<String, Integer> map){
LinkedHashMap<String, Integer> sorted = sortHashMapByValues(map);
LinkedHashMap<String, Integer> out = new LinkedHashMap<String, Integer>();
Iterator<Entry<String,Integer>> it = sorted.entrySet().iterator();
for(int i = 0; i<map.size()/2; i++){
Entry<String, Integer> e = it.next();
out.put(e.getKey(), e.getValue());
}
return out;
}

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

Categories

Resources