Search code examples
javajava-8priority-queuetreemaptreeset

How to find Top N given a list of Object?


I want to store key-value pairs <String, List> in a map and sort the entries based on the value (list of Data) of Key as per following logic:

  1. Sort the value (in the list) for each key (group) by the score in the Data objects and
  2. If the size of the map is greater than n (say n = 3) - then sort the keys(group) based on first item's (value) score and return 3. - equivalent to saying get Top 3(1 from each group) based on high score

I am looking get 1 result per group (A,B,C,D)

 import java.util.ArrayList; 

public class MyClass {

    public static void main(String args[]) {
      
       // output should be just Data(17.0, "five", "D"), Data(4.0, "two", "A"), Data(3.0, "three", "B")
      ArrayList<Data> dataList = new ArrayList<Data>();
        dataList.add(new Data(1.0, "one", "A"));
        dataList.add(new Data(4.0, "two", "A"));
        dataList.add(new Data(3.0, "three", "B"));
        dataList.add(new Data(2.0, "four", "C"));
        dataList.add(new Data(7.0, "five", "D"));
        dataList.add(new Data(17.0, "five", "D"));
        
// output should be just Data(5.0, "six", "A"), Data(3.14, "two", "B"), Data(3.14, "three", "C")
      ArrayList<Data> dataList2 = new ArrayList<Data>();
        dataList2.add(new Data(3.0, "one", "A"));
        dataList2.add(new Data(5.0, "six", "A"));
        dataList2.add(new Data(3.14, "two", "B"));
        dataList2.add(new Data(3.14, "three", "C"));
        
      System.out.println("data 1=  " + dataList.size());
      System.out.println("data 2 =  " + dataList2.size());

    }
    
  static class Data {
     double score;
     String name; 
     String group;
     
      
    public Data(double score, String name, String group) {
        score = this.score;
        name = this.name;
        group = this.group;
    }
    
    public String getName() {
        return name;
    }
     
    public String getGroup() {
        return group;
    }
    
    public double getScore() {
        return score;
    }
    }
}

I know the procedural way to do this but is there a smarter/functional way to do it in Java 8?


Solution

  • I am looking get 1 result per group (A,B,C,D)

    and

    I want to store key-value pairs <String, List>

    contradict each other somehow. But I assume that you want to have the entry with the highest score from each group and limit the result to some given size n. If this is true try something like below to get a map with top n elements having group as key and the Data object itself as value

    long n = 3;
    
    Map<String,Data> result =
                dataList.stream()
                .collect(Collectors.groupingBy(Data::getGroup,
                        Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingDouble(Data::getScore)),
                                Optional::get)))
                .entrySet()
                .stream()
                        .sorted(Comparator.comparing(e -> e.getValue().getScore(),Comparator.reverseOrder()))
                        .limit(n)
                        .collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue,(a,b) -> a, LinkedHashMap::new));
    
    result.entrySet().forEach(System.out::println);
    

    to get

    D=MyClass.Data(score=17.0, name=five, group=D)
    A=MyClass.Data(score=4.0, name=two, group=A)
    B=MyClass.Data(score=3.0, name=three, group=B)
    

    Or if you just need a sublist of the list which contains the max score elements in each group (since the group value is available in the object itself), then do something like:

    List<Data> result =
                dataList.stream()
                .collect(Collectors.groupingBy(Data::getGroup,
                        Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingDouble(Data::getScore)),
                                Optional::get)))
                .values()
                .stream()
                        .sorted(Comparator.comparingDouble(Data::getScore).reversed())
                        .limit(n)
                        .collect(Collectors.toList());
        result.forEach(System.out::println);
    
    result.forEach(System.out::println);
    

    to get

    MyClass.Data(score=17.0, name=five, group=D)
    MyClass.Data(score=4.0, name=two, group=A)
    MyClass.Data(score=3.0, name=three, group=B)