Home > Net >  Sorting a List dynamically using attributes provided at Rinetime
Sorting a List dynamically using attributes provided at Rinetime

Time:03-22

There's a Map with keys of type String as the and values represented by a list of objects as follows:

Map<String, List<ScoreAverage>> averagesMap

ScoreAverage record:

public record ScoreAverage(
    @JsonProperty("average") double average,
    @JsonProperty("name") String name
) {}

The map holds data as follows :

{
 "averagesMap":{
  "A":[
     {
        "average":4.0,
        "name":"Accounting"
     },
     {
        "average":4.0,
        "name":"company-wide"
     },
     {
        "average":4.0,
        "name":"Engineering"
     }
  ],
  "B":[
     {
        "average":3.0,
        "name":"Engineering"
     },
     {
        "average":3.0,
        "name":"company-wide"
     },
     {
        "average":3.0,
        "name":"Accounting"
     }
  ],
  "C":[
     {
        "average":2.0,
        "name":"company-wide"
     },
     {
        "average":2.0,
        "name":"Engineering"
     },
     {
        "average":2.0,
        "en":"Accounting"
     }
  ],
  "specialAverages":[
     {
        "average":2.5,
        "name":"Engineering"
     },
     {
        "average":2.5,
        "name":"company-wide"
     },
     {
        "average":2.5,
        "name":"Accounting"
     }
   ]
  }
}

What I want to achieve is to sort dynamically each list of objects in a map using the name attribute in the order specified at runtime, for instance:

1st item of list -> company-wide
2nd item of list -> Engineering
3rd item of list -> Accounting

What would be the easiest way of doing this?

CodePudding user response:

To achieve that, first you need to establish the desired order. So that it will be encapsulated in a variable and passed as a parameter at runtime to the method that will take care of sorting. With that, sorting order will be dynamic, dependent on the provided argument.

In the code below, a List is being used for that purpose. Sorting is based on the indexes that each name occupies in the sortingRule list.

The next step is to create Comparator based on it. I'm using a condition sortingRule.contains(score.name()) as precaution for the cases like typo, etc. when name doesn't appear in the sortingRule. With that, all such objects will be placed at the end of a sorted list.

 Comparator<ScoreAverage> dynamicComparator =
            Comparator.comparingInt(score -> sortingRule.contains(score.name()) ?
                                    sortingRule.indexOf(score.name()) : 
                                    sortingRule.size());

If we drop the condition comparator boils down to

Comparator.comparingInt(score -> sortingRule.indexOf(score.name()));

With that, all unidentified objects (if any) will be grouped at the beginning of a sorted list.

And finally, we need to sort every value in a map with this comparator.

Iterative implementation (note: defensive copy of every list is meant to preserve the source intact).

public static Map<String, List<ScoreAverage>> sortByRule(Map<String, List<ScoreAverage>> averagesMap,
                                                         List<String> sortingRule) {
    Comparator<ScoreAverage> dynamicComparator =
            Comparator.comparingInt(score -> sortingRule.contains(score.name()) ?
                    sortingRule.indexOf(score.name()) :
                    sortingRule.size());

    Map<String, List<ScoreAverage>> result = new HashMap<>();
    for (Map.Entry<String, List<ScoreAverage>> entry: averagesMap.entrySet()) {
        List<ScoreAverage> copy = new ArrayList<>(entry.getValue());
        copy.sort(dynamicComparator);
        result.put(entry.getKey(), copy);
    }
    return result;
}

Stream based implementation (note: lists in the source map will not get modified, each entry will be replaced with a new one).

public static Map<String, List<ScoreAverage>> sortByRule(Map<String, List<ScoreAverage>> averagesMap,
                                            List<String> sortingRule) {

     Comparator<ScoreAverage> dynamicComparator =
            Comparator.comparingInt(score -> sortingRule.contains(score.name()) ?
                                    sortingRule.indexOf(score.name()) : 
                                    sortingRule.size());

    return averagesMap.entrySet().stream()
            .map(entry -> Map.entry(entry.getKey(),
                    entry.getValue().stream()
                    .sorted(dynamicComparator)
                    .collect(Collectors.toList())))
            .collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
}

main()

public static void main(String[] args) {
    Map<String, List<ScoreAverage>> averagesMap =
            Map.of("A", List.of(new ScoreAverage(4.0, "Accounting"),
                                    new ScoreAverage(4.0, "company-wide"),
                                    new ScoreAverage(4.0, "Engineering")),
                   "B", List.of(new ScoreAverage(3.0, "Engineering"),
                                    new ScoreAverage(3.0, "company-wide"),
                                    new ScoreAverage(3.0, "Accounting")),
                   "C", List.of(new ScoreAverage(2.0, "company-wide"),
                                    new ScoreAverage(2.0, "Engineering"),
                                    new ScoreAverage(2.0, "Accounting")),
                   "specialAverages", List.of(new ScoreAverage(2.5, "Engineering"),
                                    new ScoreAverage(2.5, "company-wide"),
                                    new ScoreAverage(2.5, "Accounting")));

    List<String> sortingRule = List.of("company-wide", "Engineering", "Accounting");

    sortByRule(averagesMap, sortingRule).forEach((k, v) -> System.out.println(k   " : "   v));
}

Output

A : [ScoreAverage[average=4.0, name=company-wide], ScoreAverage[average=4.0, name=Engineering], ScoreAverage[average=4.0, name=Accounting]]
B : [ScoreAverage[average=3.0, name=company-wide], ScoreAverage[average=3.0, name=Engineering], ScoreAverage[average=3.0, name=Accounting]]
C : [ScoreAverage[average=2.0, name=company-wide], ScoreAverage[average=2.0, name=Engineering], ScoreAverage[average=2.0, name=Accounting]]
specialAverages : [ScoreAverage[average=2.5, name=company-wide], ScoreAverage[average=2.5, name=Engineering], ScoreAverage[average=2.5, name=Accounting]]

Update

It's also possible to combine the sorting rule encapsulated in a list and Comparator that will responsible for sorting elements that are not present in the sorting rule. Both sorting rule and a comparator will be provided dynamically at runtime.

For that, the method signature has to be changed (the third parameter needs to be added):

public static Map<String, List<ScoreAverage>> sortByRule(Map<String, List<ScoreAverage>> averagesMap,
                                                         List<String> sortingRule,
                                                         Comparator<ScoreAverage> downstreamComparator)

And the comparator will look like that:

Comparator<ScoreAverage> dynamicComparator =
                Comparator.<ScoreAverage>comparingInt(score -> sortingRule.contains(score.name()) ?
                                            sortingRule.indexOf(score.name()) :
                                            sortingRule.size())
                                .thenComparing(downstreamComparator);

It will group all objects with names contained in the sortingRule at the beginning of the resulting list, the rest part will be sorted in accordance with the downstreamComparator.

Method call in the main will look like that:

sortByRule(averagesMap, sortingRule, Comparator.comparing(ScoreAverage::name))
                .forEach((k, v) -> System.out.println(k   " : "   v));

If you apply these changes with and provide a sortingRule containing only one string "company-wide" you'll this output:

A : [ScoreAverage[average=4.0, name=company-wide], ScoreAverage[average=4.0, name=Accounting], ScoreAverage[average=4.0, name=Engineering]]
B : [ScoreAverage[average=3.0, name=company-wide], ScoreAverage[average=3.0, name=Accounting], ScoreAverage[average=3.0, name=Engineering]]
C : [ScoreAverage[average=2.0, name=company-wide], ScoreAverage[average=2.0, name=Accounting], ScoreAverage[average=2.0, name=Engineering]]
specialAverages : [ScoreAverage[average=2.5, name=company-wide], ScoreAverage[average=2.5, name=Accounting], ScoreAverage[average=2.5, name=Engineering]]
  • Related