Sort Map by Top Scores - java

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;
}

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)

How to find the least and most common name in an ArrayList in JAVA

I am having trouble finding the most and least common name in an ArrayList. The formula should go through a file of names and count how many common names there are in the list then print the least and most common of them. I already have the most ArrayList Part finished, it is just finding the most and least common name I am having trouble with.I have no idea how to even start it.I have tried to look online but couldn't find any. I kind of tried to figure it out but this is all I could think of is using .equals.
for (int i = 0; i< dogs.size(); i++)
if dogs.get(0).getName().equals dogs.get(i).getName();
{
}
Use a Map to collect the data, then use the Collections API to find the minimum:
List<Dog> dogs; // populate
Map<String, Integer> counts = new HashMap<>();
for (Dog dog : dogs) {
Integer count = counts.get(dog.getName());
counts.put(dog.getName(), count == null ? 1 : count + 1);
}
List<Map.Entry<String, Integer>> entries = new ArrayList<>(counts.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return Integer.compare(o2.getValue(), o1.getValue()); // Note reverse order
}
});
String leastCommonName = entries.get(0).getKey();
int leastCommonFrequency = entries.get(0).getValue();
Here's a java 8 version of finding the least-used name:
Map.Entry<String, Integer> min = counts.entrySet().stream()
.min((o1, o2) -> Integer.compare(o1.getValue(), o2.getValue())).get();
String leastCommonName = min.getKey();
int leastCommonFrequency = min.getValue();
Essentially list creation and sort is avoided, replaced with a one-liner that finds the minimum value from a stream (of Entries) using the same comparator, but as a lambda expression.
Create a Map<String, Integer>.
Loop through your ArrayList, check the Map to see if it contains the name, if it does, increment the value and put it back in the Map, it not, create a new entry and put that in.
This will give you list of the names and the number of times they appear. Run through this list (Map) and check which one has the lowest count, tracking the name as you go...
For example...
List<Dog> dogs = new ArrayList<>(25);
Map<String, Integer> dogNames = new HashMap<>(25);
for (Dog dog : dogs) {
Integer value = dogNames.get(dog);
if (value == null) {
value = 0;
}
value++;
dogNames.put(dog.getName(), value);
}
int leastCommon = Integer.MAX_VALUE;
String leastCommonName = null;
for (String name : dogNames.keySet()) {
int value = dogNames.get(name);
if (value < leastCommon) {
leastCommon = value;
leastCommonName = name;
}
}
System.out.println("Least common (" + leastCommon + ") is " + leastCommonName);

How can I sort a map contains another map by numbers

I have a HashMap of type:
HashMap<String, HashMap<String, Integer>>
This HashMap has values like
{name, {dateasString, numberOfTasks}}
I want to sort this by numberOfTasks. I can't find the way to do that.
You can't sort a HashMap, but you can get a sorted Array (or a List) of
the keys. How you want to define your sort is up to you - just modify the
comparator (or the numberOfTasks function that it calls:)
So something like this?
public static void main() {
final
HashMap<String, HashMap<String, Integer>> map = new HashMap<String, HashMap<String, Integer>>();
String[] keys = (String[]) map.keySet().toArray();
Arrays.sort(keys, new Comparator<String>() {
#Override
public int compare(String k1, String k2) {
int v1 = numberOfTasks(map.get(k1));
int v2 = numberOfTasks(map.get(k2));
return Integer.valueOf(v1).compareTo(Integer.valueOf(v2));
}
});
// 'keys' is now sorted the way you want.
}
public static int numberOfTasks(HashMap<String, Integer> map) {
int max = 0;
for (Integer i : map.values()) {
if (i > max) max = i;
}
return max;
}
Create your custom Comparator that compares elemetns according to the sum of objects, and use Arrays.sort() or Collections.sort() after populating a list/array with the data:
//populate example data:
final Map<String,Map<String,Integer>> map = new HashMap<String, Map<String,Integer>>();
map.put("x", new HashMap<String, Integer>());
map.get("x").put("t1",1);
map.get("x").put("t2",1);
map.get("x").put("t3",1);
map.put("y", new HashMap<String, Integer>());
map.get("y").put("t1",2);
map.get("y").put("t2",2);
map.get("y").put("t3",2);
map.put("z", new HashMap<String, Integer>());
map.get("z").put("t1",3);
map.get("z").put("t2",3);
map.get("z").put("t3",3);
//populate the data in a list:
List<String> list = new ArrayList<String>(map.keySet());
//sort the data with a custom comparator:
Collections.sort(list, new Comparator<String>() {
private int getSum(String s) {
int sum = 0;
for (Integer x : map.get(s).values()) {
if (x != null) sum += x;
}
return sum;
}
public int compare(String o1, String o2) {
return new Integer(getSum(o1)).compareTo(new Integer(getSum(o2)));
}
});
System.out.println(list);
Note, to improve performance you could use a caching mechanism to avoid recalculating the sum of each element every time it is being compared.
Does the second HashMap have to be a HashMap? Can you convert the second HashMap into a class specifically for this? Perhaps something like this:
private class TaskList
{
String dateAsString;
int numTasks;
public TaskList(String dateAsString, int numTasks)
{
this.dateAsString = dateAsString;
this.numTasks = numTasks;
}
public getDateAsString()
{
return dateAsString;
}
public getNumTasks()
{
return numTasks;
}
}
Then you can say HashMap<String, TaskList> and access the number of tasks directly and sort them accordingly. Even then I don't think HashMap is right for this.
I doubt there is a way to do this in the Java APIs. You will either have to do it manually by extracting the data in a separate model and sort that or redesign the model such as it will allow sorting on that field easier.
You would have to convert the hashmap to an array. If it were possible to sort a hashmap it would completely defeat the purpose of using one in the first place.
hashMap.keySet().toArray(); // returns an array of keys
hashMap.values().toArray(); // returns an array of values
Arrays.sort(array); // sorts an array
Hope that helps.

How select first N items in Java TreeMap?

Given this map
SortedMap<Integer, String> myMap = new TreeMap<Integer, String>();
Instead of a for loop is there a utility function to copy first N items to a destination map?
Using the power of Java 8+:
TreeMap<Integer, String> myNewMap = myMap.entrySet().stream()
.limit(3)
.collect(TreeMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), Map::putAll);
Maybe, but not as part of the standard Java API. And: the utility would use a loop inside.
So you'll need a loop, but you can create your own "utility" by doing it all in a static method in a utility class:
public static SortedMap<K,V> putFirstEntries(int max, SortedMap<K,V> source) {
int count = 0;
TreeMap<K,V> target = new TreeMap<K,V>();
for (Map.Entry<K,V> entry:source.entrySet()) {
if (count >= max) break;
target.put(entry.getKey(), entry.getValue());
count++;
}
return target;
}
The complexity is still O(n) (I doubt, that one can achieve O(1)) but you use it like a tool without "seeing" the loop:
SortedMap<Integer, String> firstFive = Util.putFirstEntries(5, sourceMap);
There's SortedMap.headMap() however you'd have to pass a key for the element to go up to. You could iterate N elements over Map.keySet() to find it, e.g.:
Integer toKey = null;
int i = 0;
for (Integer key : myMap.keySet()) {
if (i++ == N) {
toKey = key;
break;
}
}
// be careful that toKey isn't null because N is < 0 or >= myMap.size()
SortedMap<Integer, String> copyMap = myMap.headMap(toKey);
You can also use an ordored iterator to get the first x records, orderer by descending id for instance :
Iterator<Integer> iterator = myMap.descendingKeySet().iterator();
You can use the putAll(Map t) function to copy the items from the map to specified map.But it copies all the items. You cannot copy fixed number of items.
http://download.oracle.com/javase/1.4.2/docs/api/java/util/Map.html#putAll%28java.util.Map%29

Efficient way to get the most used keys in a HashMap - 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.

Categories

Resources