1

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?

user3407267
  • 1,524
  • 9
  • 30
  • 57
  • What does the map you mention in your question have to do with your desired result? All I can see is that you sort a list and output n elements with the largest attribute values. – Eritrean Aug 17 '21 at 18:43
  • @Eritrean Sorry updated the Question. I am looking get 1 result per group (A,B,C,D) – user3407267 Aug 17 '21 at 18:45
  • I don't understand. You say "key-value pairs ", but "{"A", one, 3.0}" doesn't have a list. It looks like a list itself or an array, but then where's the key? Can you please edit your question, so it's clear how "the following key-value pairs" should end up as. And which is the key and which is the value? – Scratte Aug 17 '21 at 19:53
  • 1
    Please provide more detail. Show before and after (desired result), and *the map is greater than n* what is `n`? And where is `{"E", five, 2.1},` in your code? – WJS Aug 17 '21 at 20:05
  • @WJS updated teh question. Sorry for the confusion – user3407267 Aug 17 '21 at 20:58

1 Answers1

1

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)
Eritrean
  • 15,851
  • 3
  • 22
  • 28
  • Yes! the assumption is correct but how does this give me top n though ? if n = 3 – user3407267 Aug 17 '21 at 19:31
  • Can I use priority queue with map .. then a comparator for priority queue based on score (reversed) but the issue here is I can't control the order of the keys – user3407267 Aug 17 '21 at 19:32
  • I want to store key-value pairs I thought with map I can group A -> List (Data(3.0, "one", "A") , Data(4.0, "two", "A")) – user3407267 Aug 17 '21 at 19:36
  • 1
    I'm afraid I'm not sure yet what your desired end result should be. Can you give the desired result for your example list `dataList` with `n = 3`? A sublist containing the elements with score values 17, 4 and 3? A map with group as key and value as ??? Or something else? – Eritrean Aug 17 '21 at 19:41
  • Just the value (data object) for n = 3, from the above, the result will be 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) in desecnding order based on each group's high score – user3407267 Aug 17 '21 at 19:47
  • 1
    @Eritrean *I'm afraid I'm not sure yet what your desired end result should be. Can you give the desired result for your example list dataList with n = 3?* Then why did you try and answer before getting clarification? – WJS Aug 17 '21 at 20:16
  • 1
    @WJS The question has been edited several times and comments have been added and the requirements and desired end result that seemed clear at the beginning have become rather unclear to me despite the editing. So I try to give OP what he is looking for but has not managed to accurately articulate what he is actually looking for. Kind of predictive reply :-). But OP may also have an xy-problem. – Eritrean Aug 17 '21 at 20:35
  • 2
    Instead of `Collectors.groupingBy(Data::getGroup, Collectors.collectingAndThen( Collectors.maxBy(Comparator.comparingDouble(Data::getScore)), Optional::get))` you can use `Collectors.toMap(Data::getGroup, Function.identity(), BinaryOperator.maxBy(Comparator.comparingDouble(Data::getScore)))` that doesn’t require dealing with `Optional`. See also [Java Streams: Replacing groupingBy and reducing by toMap](https://stackoverflow.com/q/57041896/2711488) – Holger Aug 18 '21 at 09:50