使用 Rinetime 提供的属性对列表进行动态排序

Sorting a List dynamically using attributes provided at Rinetime

有一个 Map,其键类型为 String,值由对象列表表示,如下所示:

Map<String, List<ScoreAverage>> averagesMap

ScoreAverage 记录:

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

map保存数据如下:

{
 "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"
     }
   ]
  }
}

我想要实现的是使用 name 属性按照运行时指定的顺序对地图中的每个对象列表进行动态排序,例如:

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

最简单的方法是什么?

为此,首先您需要建立所需的顺序。这样它将被封装在一个变量中,并在运行时作为参数传递给负责排序的方法。这样,排序顺序将是动态的,取决于提供的参数。

在下面的代码中,List 用于该目的。排序是根据每个namesortingRule列表中占据的索引。

下一步是基于它创建Comparator。当 name 没有出现在 sortingRule 中时,我使用条件 sortingRule.contains(score.name()) 作为预防措施,以防止拼写错误等情况。这样,所有此类对象都将放在排序列表的末尾。

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

如果我们放弃条件比较器归结为

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

这样,所有未识别的对象(如果有的话)将被分组在排序列表的开头。

最后,我们需要使用此比较器对映射中的每个值进行排序。

迭代实现注意:每个列表的防御性副本都是为了完整地保留源代码)。

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;
}

基于流的实现注意:源映射中的列表不会被修改,每个条目将被替换为一个新条目 ).

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));
}

输出

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

更新

也可以结合封装在列表中的 排序规则 和负责对 中不存在的元素进行排序的 Comparator排序规则排序规则和比较器都将在运行时动态提供。

为此,必须更改方法签名(需要添加第三个参数):

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

比较器看起来像这样:

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

它将所有名称为包含的对象分组在结果列表的开头sortingRule中,其余部分将按照[=排序29=].

main 中的方法调用如下所示:

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

如果您应用这些更改并提供仅包含一个字符串 "company-wide"sortingRule,您将得到 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]]