I am currently doing the following programming task: Prize Draw, the statement is:
To participate in a prize draw each one gives his/her firstname.
Each letter of a firstname has a value which is its rank in the English alphabet. A and a have rank 1, B and b rank 2 and so on.
The length of the firstname is added to the sum of these ranks hence a number som.
An array of random weights is linked to the firstnames and each som is multiplied by its corresponding weight to get what they call a winning number.
Example:
names: "COLIN,AMANDBA,AMANDAB,CAROL,PauL,JOSEPH" weights: [1, 4, 4, 5, 2, 1]
PauL -> som = length of firstname + 16 + 1 + 21 + 12 = 4 + 50 -> 54 The weight associated with PauL is 2 so PauL's winning number is 54 * 2 = 108.
Now one can sort the firstnames in decreasing order of the winning numbers. When two people have the same winning number sort them alphabetically by their firstnames. Task:
parameters: st a string of firstnames, we an array of weights, n a rank return: the firstname of the participant whose rank is n (ranks are numbered from 1)
Example:
names: "COLIN,AMANDBA,AMANDAB,CAROL,PauL,JOSEPH" weights: [1, 4, 4, 5, 2, 1] n: 4
The function should return: "PauL"
Note:
If st is empty return "No participants". If n is greater than the number of participants then return "Not enough participants".
I have thought the pseudocode as:
if names are empty return No participants
if n is greater than names length return Not enough participants
for each name
sum its length
for each character
sum its value in the alphabet
multiply sum by weight
store name and sum in the map
sort map descending by value
sort map alphabetically by key (if values are equal)
return key n from the map
In addition I have coded the following:
import java.util.*;
import java.util.stream.*;
class Rank {
public static String nthRank(String st, Integer[] we, int n) {
System.out.println("\n\n\nNames: "+st+" weights: "+Arrays.toString(we)+" n: "+n);
if(st.isEmpty()) return "No participants";
String[] names = st.split(",");
if(n > names.length) return "Not enough participants";
String alphabet = "abcdefghijklmnopqrstuvwxyz";
int sum = 0;
Map<String, Integer> map = new HashMap<String, Integer>();
for(int i = 0; i < names.length; i++){
String currentName = names[i];
sum += currentName.length();
for(int j = 0; j < currentName.length(); j++){
char currentChar = Character.toLowerCase(currentName.charAt(j));
sum += alphabet.indexOf(currentChar) + 1;
}
map.put(currentName, sum*we[i]);
System.out.println("Map: "+map.toString());
sum = 0;
}
map = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
System.out.println("\n\nSorted Map: "+map.toString());
Object[] namesByWinningNumber = map.keySet().toArray();
Arrays.sort(namesByWinningNumber);
System.out.println("\n\n\nSorted names array: "+ Arrays.toString(namesByWinningNumber));
return map.keySet().toArray()[n-1].toString();
}
}
When input is:
Names: William,Willaim,Olivia,Olivai,Lily,Lyli weights: [1, 1, 1, 1, 1, 1] n: 1
Expected is:
Willaim
But the code outputs:
William
Being the trace:
Names: William,Willaim,Olivia,Olivai,Lily,Lyli weights: [1, 1, 1, 1, 1, 1] n: 1
Map: {William=86}
Map: {William=86, Willaim=86}
Map: {Olivia=74, William=86, Willaim=86}
Map: {Olivia=74, Olivai=74, William=86, Willaim=86}
Map: {Olivia=74, Olivai=74, William=86, Willaim=86, Lily=62}
Map: {Olivia=74, Olivai=74, William=86, Willaim=86, Lily=62, Lyli=62}
Sorted Map: {William=86, Willaim=86, Olivia=74, Olivai=74, Lily=62, Lyli=62}
Sorted names array: [Lily, Lyli, Olivai, Olivia, Willaim, William]
So, as we see, first I sort the map by values in descending order. Then I thought if I get the keys and I sort them alphabetically, it would just take care of the ones with exact same values; however it sorts all.
How could we sort a Map descending by values and then alphabetically by keys‽‽‽
I have also read:
- Sort a Map<Key, Value> by values
- What's the simplest way to print a Java array?
- Sort an array in Java
- Why doesn't java.util.Set have get(int index)?
- Printing Java Collections Nicely (toString Doesn't Return Pretty Output)
- Method to extract all keys from LinkedHashMap into a List
- How get value from LinkedHashMap based on index not on key?
- Sort a Map<Key, Value> by values
- How do I efficiently iterate over each entry in a Java Map?
EDIT:
I read: how to write java 8 Comparator for Map values inside Map.Entry
I have also tried to write my own Comparator:
Comparator<Map.Entry> outerComparator = (pair1, pair2) -> {
if ((int)pair1.getValue() > (int)pair2.getValue()){
return 1;
}else if ((int)pair1.getValue() < (int)pair2.getValue()){
return -1;
}else{
return pair1.getKey().toString().compareTo(pair2.getKey().toString());
}
};
And use it as follows:
map = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(outerComparator))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
However, our console says:
./src/main/java/Rank.java:40: error: incompatible types: inference variable V has incompatible bounds
.sorted(Map.Entry.comparingByValue(outerComparator))
^
equality constraints: Integer
lower bounds: Entry,Object
where V,K are type-variables:
V extends Object declared in method <K,V>comparingByValue(Comparator<? super V>)
K extends Object declared in method <K,V>comparingByValue(Comparator<? super V>)
./src/main/java/Rank.java:41: error: incompatible types: cannot infer type-variable(s) T,K#1,U,M,K#2,V#1
.collect(Collectors.toMap(
^
(argument mismatch; invalid method reference
method getKey in interface Entry<K#3,V#2> cannot be applied to given types
required: no arguments
found: Object
reason: actual and formal argument lists differ in length)
where T,K#1,U,M,K#2,V#1,K#3,V#2 are type-variables:
T extends Object declared in method <T,K#1,U,M>toMap(Function<? super T,? extends K#1>,Function<? super T,? extends U>,BinaryOperator<U>,Supplier<M>)
K#1 extends Object declared in method <T,K#1,U,M>toMap(Function<? super T,? extends K#1>,Function<? super T,? extends U>,BinaryOperator<U>,Supplier<M>)
U extends Object declared in method <T,K#1,U,M>toMap(Function<? super T,? extends K#1>,Function<? super T,? extends U>,BinaryOperator<U>,Supplier<M>)
M extends Map<K#1,U> declared in method <T,K#1,U,M>toMap(Function<? super T,? extends K#1>,Function<? super T,? extends U>,BinaryOperator<U>,Supplier<M>)
K#2 extends Object declared in class LinkedHashMap
V#1 extends Object declared in class LinkedHashMap
K#3 extends Object declared in interface Entry
V#2 extends Object declared in interface Entry
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
2 errors
Being the complete code until this point:
import java.util.*;
import java.util.stream.*;
class Rank {
public static String nthRank(String st, Integer[] we, int n) {
System.out.println("\n\n\nNames: "+st+" weights: "+Arrays.toString(we)+" n: "+n);
if(st.isEmpty()) return "No participants";
String[] names = st.split(",");
if(n > names.length) return "Not enough participants";
String alphabet = "abcdefghijklmnopqrstuvwxyz";
int sum = 0;
Map<String, Integer> map = new HashMap<String, Integer>();
for(int i = 0; i < names.length; i++){
String currentName = names[i];
sum += currentName.length();
for(int j = 0; j < currentName.length(); j++){
char currentChar = Character.toLowerCase(currentName.charAt(j));
sum += alphabet.indexOf(currentChar) + 1;
}
map.put(currentName, sum*we[i]);
System.out.println("Map: "+map.toString());
sum = 0;
}
Comparator<Map.Entry> outerComparator = (pair1, pair2) -> {
if ((int)pair1.getValue() > (int)pair2.getValue()){
return 1;
}else if ((int)pair1.getValue() < (int)pair2.getValue()){
return -1;
}else{
return pair1.getKey().toString().compareTo(pair2.getKey().toString());
}
};
map = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(outerComparator))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
System.out.println("\n\nSorted Map: "+map.toString());
return map.keySet().toArray()[n-1].toString();
}
}
As a new attempt I wrote it as a separated class:
class MapComparator implements Comparator<Map.Entry> {
@Override
public int compare(Map.Entry pair1, Map.Entry pair2) {
int valueCompare = - ( pair1.getValue().compareTo(pair2.getValue()) );
int nameCompare = pair1.getKey().compareTo(pair2.getKey());
if (valueCompare == 0) {
return ((nameCompare == 0) ? valueCompare : nameCompare);
} else {
return valueCompare;
}
}
}
And I thought it could be used nicely as:
map = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(MapComparator.compare()))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
And then our console claims:
./src/main/java/Rank.java:40: error: method compare in class Rank.MapComparator cannot be applied to given types;
.sorted(Map.Entry.comparingByValue(MapComparator.compare()))
^
required: Entry,Entry
found: no arguments
reason: actual and formal argument lists differ in length
./src/main/java/Rank.java:41: error: incompatible types: cannot infer type-variable(s) T,K#1,U,M,K#2,V#1
.collect(Collectors.toMap(
^
(argument mismatch; invalid method reference
method getKey in interface Entry<K#3,V#2> cannot be applied to given types
required: no arguments
found: Object
reason: actual and formal argument lists differ in length)
where T,K#1,U,M,K#2,V#1,K#3,V#2 are type-variables:
T extends Object declared in method <T,K#1,U,M>toMap(Function<? super T,? extends K#1>,Function<? super T,? extends U>,BinaryOperator<U>,Supplier<M>)
K#1 extends Object declared in method <T,K#1,U,M>toMap(Function<? super T,? extends K#1>,Function<? super T,? extends U>,BinaryOperator<U>,Supplier<M>)
U extends Object declared in method <T,K#1,U,M>toMap(Function<? super T,? extends K#1>,Function<? super T,? extends U>,BinaryOperator<U>,Supplier<M>)
M extends Map<K#1,U> declared in method <T,K#1,U,M>toMap(Function<? super T,? extends K#1>,Function<? super T,? extends U>,BinaryOperator<U>,Supplier<M>)
K#2 extends Object declared in class LinkedHashMap
V#1 extends Object declared in class LinkedHashMap
K#3 extends Object declared in interface Entry
V#2 extends Object declared in interface Entry
./src/main/java/Rank.java:54: error: cannot find symbol
int valueCompare = - ( pair1.getValue().compareTo(pair2.getValue()) );
^
symbol: method compareTo(Object)
location: class Object
./src/main/java/Rank.java:55: error: cannot find symbol
int nameCompare = pair1.getKey().compareTo(pair2.getKey());
^
symbol: method compareTo(Object)
location: class Object
4 errors
To sum up, currently the complete code is:
import java.util.*;
import java.util.stream.*;
class Rank {
public static String nthRank(String st, Integer[] we, int n) {
System.out.println("\n\n\nNames: "+st+" weights: "+Arrays.toString(we)+" n: "+n);
if(st.isEmpty()) return "No participants";
String[] names = st.split(",");
if(n > names.length) return "Not enough participants";
String alphabet = "abcdefghijklmnopqrstuvwxyz";
int sum = 0;
Map<String, Integer> map = new HashMap<String, Integer>();
for(int i = 0; i < names.length; i++){
String currentName = names[i];
sum += currentName.length();
for(int j = 0; j < currentName.length(); j++){
char currentChar = Character.toLowerCase(currentName.charAt(j));
sum += alphabet.indexOf(currentChar) + 1;
}
map.put(currentName, sum*we[i]);
System.out.println("Map: "+map.toString());
sum = 0;
}
Comparator<Map.Entry> outerComparator = (pair1, pair2) -> {
if ((int)pair1.getValue() > (int)pair2.getValue()){
return 1;
}else if ((int)pair1.getValue() < (int)pair2.getValue()){
return -1;
}else{
return pair1.getKey().toString().compareTo(pair2.getKey().toString());
}
};
map = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(MapComparator.compare()))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
System.out.println("\n\nSorted Map: "+map.toString());
return map.keySet().toArray()[n-1].toString();
}
class MapComparator implements Comparator<Map.Entry> {
@Override
public int compare(Map.Entry pair1, Map.Entry pair2) {
int valueCompare = - ( pair1.getValue().compareTo(pair2.getValue()) );
int nameCompare = pair1.getKey().compareTo(pair2.getKey());
if (valueCompare == 0) {
return ((nameCompare == 0) ? valueCompare : nameCompare);
} else {
return valueCompare;
}
}
}
}