Comparator creates duplicates in TreeMap - java

I would like to sort my HashMap (or TreeMap) by values. I kind of achieved this by creating a custom Comparator that sorts after value. However whenever I put in all my entries from the HashMap again I get duplicates.
How can I sort by values without creating duplicates?
CODE
public class Test {
public static void main(String[] args) {
HashMap<Integer, String> hMap = new HashMap<Integer, String>();
ValueComparator vc = new ValueComparator(hMap);
TreeMap<Integer, String> tMap = new TreeMap<Integer, String>(vc);
hMap.put(0, "b");
hMap.put(1, "c");
hMap.put(2, "a");
tMap.putAll(hMap);
tMap.putAll(hMap);
for (Map.Entry<Integer, String> entry : tMap.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
class ValueComparator implements Comparator<Integer> {
Map<Integer, String> base;
public ValueComparator(Map<Integer, String> base) {
this.base = base;
}
public int compare(Integer a, Integer b) {
if (base.get(a).charAt(0) >= base.get(b).charAt(0))
return 1;
else return -1;
}
}
OUTPUT
2 a
2 a
0 b
0 b
1 c
1 c

You need to modify logic as below, handle all three cases of -1, 0 and 1
public int compare(Integer a, Integer b) {
if (base.get(a).charAt(0) == base.get(b).charAt(0))
return 0;
else if (base.get(a).charAt(0) > base.get(b).charAt(0))
return 1;
else
return -1;
}
output
2 a
0 b
1 c

The compare method should return 0 if both objects are equal. In your implementation, you're returning 1, and thus the map does not recognize duplicates properly.
One way to solve this is to reuse Character.compare to compare two chars:
public int compare(Integer a, Integer b) {
return Character.compare
(base.get(a).charAt(0), base.get(b).charAt(0));
}

Your comparator contract is wrong. the compare method contract says:
Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
Your code is only doing 1 and -1
what about the 0?

Related

How to get and compare the value results of a map<Integer, Integer>

By calling a method ,at my case countInNumbers, is returning results as an array.
System.out.println(countIntInNumbers(Array));
result:
{1=17, 2=10, 3=16, 4=17, 5=13, 6=22, 7=10, 8=15, 9=16, 10=19, 11=11, 12=15, 13=16, 14=13, 15=19, 16=17, 17=13, 18=21, 19=19, 20=15,}
I try to separate the numbers on different table depending their total value.
Example... I want to display the numbers that their total is between 3 and 4 to separate table than the other numbers.
Facing this problem cause the results as you may notice are Map since i am new in Java and I am so confused at this point.
Anyone can suggest from something to start of?
Updated:::
countIntInNumbers method as follows
public static Map<Integer, Integer> countIntInNumbers(int[][] mat) {
Map<Integer, Integer> intOccurences = new HashMap<>();
for (int[] row : mat) {
for (int intInRow : row) {
Integer occurences = intOccurences.get(intInRow);
if (occurences == null) { // first occurrence
intOccurences.put(intInRow, 1);
} else { // increment
intOccurences.put(intInRow, occurences.intValue() + 1);
}
}
}
return intOccurences;
I try to separate the numbers on different table depending their total value. Example... I want to print all numbers that their total is between 3 and 4 to separate table than the other numbers.
We are not sure what you are asking here but if you mean that you want to display the numbers which have a total between 2 numbers then you could do something like:
private void printNumbers(Map<Integer, Integer> intOccurences, int minTotal, int maxTotal){
boolean first = false;
System.out.print("{");
for (Map.Entry<Integer, Integer> entry : intOccurences.entrySet()) {
int total = entry.getValue();
if (total >= minTotal && total <= maxTotal) {
if (first) {
first = false;
} else {
System.out.print(", ");
}
System.out.print(entry.getKey() + "=" + total);
}
}
System.out.print("}");
}
If you are taking about copying the values to a new map then maybe something like:
private Map<Integer, Integer> extractNumbers(Map<Integer, Integer> intOccurences,
int minTotal, int maxTotal) {
Map<Integer, Integer> result = new HashMap<>();
for (Map.Entry<Integer, Integer> entry : intOccurences.entrySet()) {
int total = entry.getValue();
if (total >= minTotal && total <= maxTotal) {
result.put(entry.getKey(), total);
}
}
// not sure if you want to remove the ones from the original map
return result;
}
If you want to compare the value of a map just get it by key. Then since the values of the map are of wrapper Integer you can compare using ==, >=, <= since the Integer equals() method simply compares the int value it wraps with the other Integer's int value. In example:
// Adding some test values to the map
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 5);
map.put(2, 6);
map.put(3, 5);
// Get values by key map.get(key)
// Compare values map.get(key) == map.get(key) or use >=, <=
System.out.println(map.get(1) <= map.get(2)); // true
System.out.println(map.get(1) == map.get(3)); // true
System.out.println(map.get(1) >= map.get(2)); // false
In your countIntInNumbers it seems you are just returning and printing the map by using its toString() method. If I got you right you want to print the keys which values are between 3 and 4. In this case the values are Integer so there will not be any value between 3 and 4 other than the integers themselves.
Okay after seeing your edit, convert your raw matrix to a map and then search for the values you need, and put them into a new map. Something like this:
public static Map<Integer, Integer> countIntInNumbers(int[][] mat) {
Map<Integer, Integer> matConvertedToMap = new HashMap<>();
for(int i=0; i<mat.length; i++)
{
matConvertedToMap.put(mat[i][0], mat[i][1]);
}
Map<Integer, Integer> intOccurences = new HashMap<>();
for (Map.Entry<Integer, Integer> entry : matConvertedToMap.entrySet())
{
if(entry.getValue() == 3 || entry.getValue() == 4)
{
intOccurences.put(entry.getKey(), entry.getValue());
}
}
return intOccurences;
}
Not sure what the comparison really is and what you are expected to return but that should give you a general feeling of how to iterate through the map.

How can I have a treeMap which is sorted by value keep being sorted when I change elements?

I have a map of string keys and int values, I to sort them, and keep them sorted when I change values.
I tried using a treemap for the sorted pairs and a normal map for the unsorted paris so I can use it in the comparator, but after a one value passes the other I get a null expection, this is the defenition:
public static TreeMap<String, Long> countryData;
public static ValueComparator bvc;
public static void setCountryData(HashMap<String, Long> map){
bvc = new ValueComparator(map);
countryData = new TreeMap<String, Long>(bvc);
countryData.putAll(map);
System.out.println(Arrays.toString(countyNames));
System.out.println(countryData.values());
}
public static class ValueComparator implements Comparator<String> {
Map<String, Long> base;
public ValueComparator(Map<String, Long> base) {
this.base = base;
}
public int compare(String a, String b) {
if(base.get(a).equals(base.get(b))){
return 0;
}
if (base.get(a) > base.get(b)) {
return -1;
} else {
return 1;
}
}
}
this is how I change the values:
General.bvc.base.put(country, newValue);
General.countryData.put(country, newValue);
after one value passes another and I try to acces it, I get a null, how can I Do this?
How can I have a TreeMap which is sorted by value ..
You can't. They are sorted by key.
...
Irrelevant.

Get top 10 values in hash map

I am trying to figure out how could I get the top 10 values from the HashMap. I was initially trying to use the TreeMap and have it sort by value and then take the first 10 values however it seems that that is not the option, as TreeMap sorts by key.
I want to still be able to know which keys have the highest values, the K, V of the map are String, Integer.
Maybe you should implement the Comparable Interface to your value objects stored in the hashmap.
Then you can create a array list of all values:
List<YourValueType> l = new ArrayList<YourValueType>(hashmap.values());
Collection.sort(l);
l = l.subList(0,10);
Regards
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class Testing {
public static void main(String[] args) {
HashMap<String,Double> map = new HashMap<String,Double>();
ValueComparator bvc = new ValueComparator(map);
TreeMap<String,Double> sorted_map = new TreeMap<String,Double>(bvc);
map.put("A",99.5);
map.put("B",67.4);
map.put("C",67.4);
map.put("D",67.3);
System.out.println("unsorted map: "+map);
sorted_map.putAll(map);
System.out.println("results: "+sorted_map);
}
}
class ValueComparator implements Comparator<String> {
Map<String, Double> base;
public ValueComparator(Map<String, Double> 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
}
}
I am afraid you'll have to iterate over the entire map. Heap is a commonly-used data structure for finding top K elements, as explained in this book.
If you are trying to get the 10 highest values of the map (assuming the values are numeric or at least implementing Comparable) then try this:
List list = new ArrayList(hashMap.values());
Collections.sort(list);
for(int i=0; i<10; i++) {
// Deal with your value
}
Let's assume you have a Map, but this example can work for any type of
Map<String, String> m = yourMethodToGetYourMap();
List<String> c = new ArrayList<String>(m.values());
Collections.sort(c);
for(int i=0 ; i< 10; ++i) {
System.out.println(i + " rank is " + c.get(i));
}
I base my answer in this one from sk2212
First you need to implement a descending comparator:
class EntryComparator implements Comparator<Entry<String,Integer>> {
/**
* Implements descending order.
*/
#Override
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
if (o1.getValue() < o2.getValue()) {
return 1;
} else if (o1.getValue() > o2.getValue()) {
return -1;
}
return 0;
}
}
Then you can use it in a method such as this one for the attribute "hashmap":
public List<Entry<String,Integer>> getTopKeysWithOccurences(int top) {
List<Entry<String,Integer>> results = new ArrayList<>(hashmap.entrySet());
Collections.sort(results, new EntryComparator());
return results.subList(0, top);
}
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
// Initialize map
System.out.println(getTopKeysWithOccurences(map, 10));
}
public static List<Entry<String,Integer>> getTopKeysWithOccurences(Map mp, int top) {
List<Entry<String,Double>> results = new ArrayList<>(mp.entrySet());
Collections.sort(results, (e1,e2) -> e2.getValue() - e1.getValue());
//Ascending order - e1.getValue() - e2.getValue()
//Descending order - e2.getValue() - e1.getValue()
return results.subList(0, top);
}

How to sort out a TreeMap<String, Integer>?

I have a map: TreeMap<String, Integer> m = new TreeMap<>(); where I have a whole alphabet and values, which shows how many times each letter was found in my text.
I want to sort that map in descending count order; that is, the most frequent letter is on the first line, and the last line of output indicates the least frequent letter. If two letters have the same frequency, then the letter which comes first in the alphabet must appear first. How to make it?
I tried with Comparator:
public int compare(String a, String b) {
if (base.get(a) >= base.get(b) && a.compareToIgnoreCase(b) < 0) {
return -1;
} else {
return 1;
}
}
but still, its not it, the output is:
D 3
E 3
A 2
S 5
Guys ... Found this before, this didnt help at all. Good output should be:
S 5
D 3
E 3
A 2
Your comparator does not look right - this should work better:
public int compare(String a, String b) {
if (base.get(a) > base.get(b)) {
return -1;
} else if (base.get(a) < base.get(b)) {
return 1;
} else {
int stringCompare = a.compareToIgnoreCase(b);
return stringCompare == 0 ? 1 : stringCompare; // returning 0 would merge keys
}
}
As the natural sort has nothing in common with your sorting wish:
List<Map.Entry<String, Integer>> entries = new ArrayList<>(m.entrieSet());
Collections.sort(entries, new Comparator<Map.Entry<String, Integer>>() {
#Override
public int compare(Map.Entry<String, Integer >a, Map.Entry<String, Integer>b) {
if (a.getValue() < b.getValue()) { // Descending values
return 1;
} else if (a.getValue() > b.getValue()) {
return -1;
}
return -a.getKey().compareTo(b.getKey()); // Descending keys
}
});

Merging maps by adding/subtracting corresponding values

I have been wondering for a few days now, what could be the least messy approach to my problem. I have a set of 10 enum types e.g. { ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE } and I use these enums as keys in a map
Map<MyEnumType, Integer> myMap;
Then I generate 100 or so of these maps, with the same keys but different values. What is the best practice for adding all of these maps into one? I mean adding up all the value of those 100 maps I have generated.
Iterate over the enum values, and for each enum value, iterate over the maps (or vice-versa):
Map<MyEnumType, Integer> sumMap = new EnumMap<MyEnumType, Integer>(MyEnumType.class);
for (MyEnumType e : MyEnumType.values()) {
int sum = 0;
for (Map<MyEnumType, Integer> map : maps) {
sum += map.get(e); // you might want a null check here
}
sumMap.put(e, sum);
}
Other option would be to create class specific for summing integers instead of overriding previous values. Here's an examples how it's done with anonymous class:
public class MapSummer {
private final Map<MyEnumType, Integer> sumMap = new HashMap<MyEnumType, Integer>() {
#Override
public Integer put(MyEnumType key, Integer value) {
return super.put(key, get(key) + value);
}
#Override
public Integer get(Object key) {
return super.get(key) != null ? super.get(key) : 0;
}
};
public Map<MyEnumType, Integer> sum(List<Map<MyEnumType, Integer>> mapList) {
for (Map<MyEnumType, Integer> map : mapList) {
sumMap.putAll(map);
}
return sumMap;
}
}
enum MyEnumType {
ONE, TWO, THREE, FOUR;
}
And unit test:
public class MapSummerTest {
private final MapSummer summer = new MapSummer();
#Test
public void shouldSumValuesInMap() {
final Map<MyEnumType, Integer> map1 = new HashMap<MyEnumType, Integer>() {{
put(ONE, 1);
put(TWO, 2);
}};
final Map<MyEnumType, Integer> map2 = new HashMap<MyEnumType, Integer>() {{
put(TWO, 2);
put(THREE, 3);
}};
final Map<MyEnumType, Integer> sumMap = summer.sum(Arrays.asList(map1, map2));
assertThat(sumMap.get(ONE), equalTo(1));
assertThat(sumMap.get(TWO), equalTo(4));
assertThat(sumMap.get(THREE), equalTo(3));
assertThat(sumMap.get(FOUR), equalTo(0));
}
}

Categories