Java 8流 - 按最大重复项排序(降序)

4
使用Java 8流,我正在尝试按字段的一部分(组名)最大重复项降序排序列表。 只需使用冒号前的第一部分:第二部分不相关。独特的行记录顺序不相关。 (我只是试图根据最大重复项将重复记录放在顶部。)
测试数据:
100 : 200
200 : 207
200 : 203
200 : 201
300 : 202
103 : 201
103 : 202

期望结果:

200 : 207
200 : 203
200 : 201
103 : 201
103 : 202
100 : 200
300 : 202

我尝试了下面的代码,它能正确返回订单。但是只有分组数据而不是原始完整记录并排序。
200=3
103=2
100=1
300=1

Java 代码

@Test
public void testSplit2Optimsation() {

    List<CompatibilityRule> rules = new ArrayList<>();
    CompatibilityRule compatibilityRule1 = new CompatibilityRule();
    compatibilityRule1.setGroupname("100 : 200");

    CompatibilityRule compatibilityRule2 = new CompatibilityRule();
    compatibilityRule2.setGroupname("200 : 207");

    CompatibilityRule compatibilityRule3 = new CompatibilityRule();
    compatibilityRule3.setGroupname("200 : 203");

    CompatibilityRule compatibilityRule4 = new CompatibilityRule();
    compatibilityRule4.setGroupname("200 : 201");

    CompatibilityRule compatibilityRule5 = new CompatibilityRule();
    compatibilityRule5.setGroupname("300 : 202");

    CompatibilityRule compatibilityRule6 = new CompatibilityRule();
    compatibilityRule6.setGroupname("102 : 202");

    CompatibilityRule compatibilityRule7 = new CompatibilityRule();
    compatibilityRule7.setGroupname("103 : 202");

    rules.add(compatibilityRule1);
    rules.add(compatibilityRule2);
    rules.add(compatibilityRule3);
    rules.add(compatibilityRule4);
    rules.add(compatibilityRule5);
    rules.add(compatibilityRule6);
    rules.add(compatibilityRule7);



    rules.stream()
            .map(r -> r.getGroupname().split(":")[0].trim())
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
            .entrySet().stream()
            .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
            .forEach(System.out::println);

}
2个回答

3
有趣的是,您的输入数据与Java代码中的实际样本不符,并且您已经接受了一个答案,该答案未按您想要的格式打印信息:在您的代码中,您希望有条目,而接受的答案则使用了List...但无论如何,考虑到您的字面问题,您的方法存在问题,因为一旦您执行了.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()),由于之前的map操作,您“丢失”了groupName的第二部分。您可以使用Collectors::mapping来解决这个问题:
    Pattern p = Pattern.compile("\\s:\\s");

    rules.stream()
         .map(CompatibilityRule::getGroupName)
         .collect(Collectors.groupingBy(
             x -> p.splitAsStream(x)
                   .findFirst()
                   .orElseThrow(),
             Collectors.mapping(
                 x -> p.splitAsStream(x).skip(1).findFirst().orElseThrow(),
                 Collectors.toList())
         ))
         .entrySet()
         .stream()
         .sorted(Map.Entry.comparingByValue(Comparator.comparingInt(List<String>::size).reversed()))
         .flatMap(x -> x.getValue().stream()
                        .map(y -> new SimpleEntry<>(x.getKey(), y)))
         .forEachOrdered(System.out::println);

问题并没有说明最终结果必须是Map.Entry实例的列表;它只显示了文本输出。此外,您可以像.sorted(Map.Entry.comparingByValue(Comparator.comparingInt(List<?>::size).reversed()))这样简化比较器。 - Holger
@Holger 哦,好棒的观点。有趣的是Intellij不编译List<?>::size,但会编译List<String>::size - Eugene
splitAsStream 方法复杂且代价高昂。我会使用类似 Pattern p = Pattern.compile("(.*?)\\s:\\s(.*)", Pattern.DOTALL);.map(CompatibilityRule::getGroupName).map(p::matcher).filter(Matcher::find).collect(Collectors.groupingBy(m -> m.group(1), Collectors.mapping(m -> m.group(2), Collectors.toList()))) 的方法。 - Holger
@Holger的建议确实更简单,但我认为你不需要一个非贪婪的第一组或Pattern.DOTALL - Eugene
@Holger 无可挑剔!像往常一样(有时我认为这是理所当然的)。我完全明白你的意思。再次感谢你。 - Eugene
显示剩余3条评论

1
这是一个可行的例子。我希望这可以解决你的问题。
方法:
分组会给你一个包含列表的映射,你只需要按其大小排序,然后展平它们,最后将它们收集为列表。
代码:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Play {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("100 : 200",
                "200 : 207",
                "200 : 203",
                "200 : 201",
                "300 : 202",
                "103 : 201",
                "103 : 202");
        List<String> processedList = list.stream().collect(Collectors.groupingBy(string -> string.split(":")[0].trim()))
                .values().stream().sorted((list1, list2) -> Integer.compare(list2.size(), list1.size()))
                .flatMap(List::stream).collect(Collectors.toList());

        System.out.println(processedList);
    }
}

你可以尝试删除冗余的运算符,如果有的话。你可以使用类的getter方法访问字符串,并按照示例所示的方法进行操作。

1
谢谢,很好,伙计。流英雄 :-) - Jay
2
不要使用减号来反转比较器的结果。由于返回值的大小未指定,它可能是 Integer.MIN_VALUE,在这种情况下,翻转符号将失败。您可以改用 (list1, list2) -> Integer.compare(list2.size(), list1.size())。或者 Comparator.comparingInt(List<?>::size).reversed()。或者 Comparator.comparingInt(l -> -l.size()),因为列表的大小永远不会是负数,所以这是安全的。而且,您可以使用 string.replaceFirst("\\s*:.*", "") 来代替 string.split(":")[0].trim(),后者会填充所有不需要的子字符串到数组中。 - Holger
1
不需要创建一个ArrayList来复制 Arrays.asList返回的列表。 - Klitos Kyriacou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接