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]]