Home > Software engineering >  Java List Group by Model property adding values
Java List Group by Model property adding values

Time:10-01

I have a List of Teams with the following properties:

public class Team {
private String name; //get;set
private int score;//get;set
}

Is there a way to group the results of the list by Name while adding the scores? The List can contain multiple of the same team but with different scores:

  • TeamA - 3
  • TeamB - 3
  • TeamA - 3
  • TeamC - 0
  • TeamB - 1
  • TeamD - 1
  • TeamE - 1

And the final list that I want to achive is something like this (ordered by score and when draw, alphabetically):

  • TeamA - 6
  • TeamB - 4
  • TeamD - 1
  • TeamE - 1
  • TeamC - 0

Right now, I got this with the help of frascu's answer:

teams.sort(Comparator.comparing(Team::getName)
            .thenComparing(Team::getScore, Comparator.reverseOrder()));
Map<String, Integer> map = teams.stream()
            .collect(groupingBy(Team::getName, summingInt(Team::getScore)));

But when I print the result from the map I get this:

  • TeamA - 6
  • TeamB - 4
  • TeamC - 0
  • TeamD - 1
  • TeamE - 1

The score 0 is being ordered as higher than 1

CodePudding user response:

It's not necessary to sort the source list unless you don't want it to be sorted as well.

Note that by sorting the source list you can not ensure that entries of the map would be stored in sorted order because the map is expected to contain the accumulated score (i.e. multiple elements in the list could contribute to a single map entry). Hence, sorting has to be applied right in the stream after grouping the data by team-name and generating the total score of each team.

In order to maintain entries in sorted order, you need a Map implementation which is capable of maintaining the order. When mapFactory is not specified, collector would give you a general purpose implementation (for now it's HashMap) which doesn't meet this requirement.

The possible option is to use a LinkedHashMap. Note: because entries need to be ordered by both value and key, you can't achieve required ordering with a TreeMap, which maintains sorted order based on keys.

That how it might be implemented:

List<Team> teams = List.of(
    new Team("TeamA", 3),
    new Team("TeamB", 3),
    new Team("TeamA", 3),
    new Team("TeamC", 0),
    new Team("TeamB", 1),
    new Team("TeamD", 1),
    new Team("TeamE", 1)
);
        
Map<String, Integer> scoreByName = teams.stream()
    .collect(Collectors.toMap(
        Team::getName,     // keyMapper
        Team::getScore,    // valueMapper
        Integer::sum       // mergeFunction
    ))
    .entrySet().stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed()
        .thenComparing(Map.Entry.comparingByKey()))
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        Map.Entry::getValue,
        (left, right) -> { throw new AssertionError("duplicates are not expected"); }, // because the source of the stream is a Map, and there couldn't be a key that occure more than once
        LinkedHashMap::new // mapFactory
    ));
        
scoreByName.forEach((k, v) -> System.out.println(k   " -> "   v));

Output:

TeamA -> 6
TeamB -> 4
TeamD -> 1
TeamE -> 1
TeamC -> 0

A link to Online Demo

CodePudding user response:

Using thenComparing you can apply a second sort on the elements that have the same name. In your case, you need to call reverseOrder in order to apply descending order to the score.

list.sort(Comparator.comparing(Team::getName)
                    .thenComparing(Team::getScore, Comparator.reverseOrder()));
  • Related