0

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:

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


}
Zabuzard
  • 25,064
  • 8
  • 58
  • 82
Yone
  • 2,064
  • 5
  • 25
  • 56
  • 2
    Create a list of objects (where each object contains the key and the value of your original map), and sort that list. – JB Nizet Nov 23 '19 at 12:47

1 Answers1

0

In regards to the errors:

./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()))

The compare method in MapComparator is not static. Besides the Map.Entry.comparingByValue method in Map.Entry takes a Comparator, not the result from MapComparator.compare().

Here, I think you intended to sort by comparing the map entries, using MapComparator, not by the values of the map (which is what comparingByValue would do). So you want:

map = map.entrySet().stream().sorted(new MapComparator()))

But you MapComparator should also be implementing Comparator<Map.Entry<String, Integer>> because of the generics on Map.Entry.

Klarth
  • 2,035
  • 14
  • 26