java 8 stream groupingBy复合变量之和

java 8 stream groupingBy sum of composite variable

我有一个 class Something 其中包含一个实例变量 Anything.

class Anything {
    private final int id;
    private final int noThings;

    public Anything(int id, int noThings) {
        this.id = id;
        this.noThings = noThings;
    }
}

class Something {
    private final int parentId;
    private final List<Anything> anythings;

    private int getParentId() {
        return parentId;
    }

    private List<Anything> getAnythings() {
        return anythings;
    }

    public Something(int parentId, List<Anything> anythings) {
        this.parentId = parentId;
        this.anythings = anythings;
    }
}

给出 Somethings

的列表
List<Something> mySomethings = Arrays.asList(
    new Something(123, Arrays.asList(new Anything(45, 65),
                                     new Anything(568, 15), 
                                     new Anything(145, 27))),
    new Something(547, Arrays.asList(new Anything(12, 123),
                                     new Anything(678, 76), 
                                     new Anything(98, 81))),
    new Something(685, Arrays.asList(new Anything(23, 57),
                                     new Anything(324, 67), 
                                     new Anything(457, 87))));

我想对它们进行排序,以便 Something 对象根据(Anything 对象)noThings 的总降序总和进行排序,然后根据(Anything 对象) noThings

123 = 65+15+27 = 107(3rd)
547 = 123+76+81 = 280 (1st)
685 = 57+67+87 = 211 (2nd)

所以我最终得到

List<Something> orderedSomethings = Arrays.asList(
    new Something(547, Arrays.asList(new Anything(12, 123),
                                     new Anything(98, 81), 
                                     new Anything(678, 76))),
    new Something(685, Arrays.asList(new Anything(457, 87),
                                     new Anything(324, 67), 
                                     new Anything(23, 57))),
    new Something(123, Arrays.asList(new Anything(45, 65),
                                     new Anything(145, 27), 
                                     new Anything(568, 15))));

我知道我可以获得每个父 Anything 的列表 Id

Map<Integer, List<Anythings>> anythings
            = mySomethings.stream()
            .collect(Collectors.toMap(p->p.getParentId(),
                    p->p.getAnythings()))
            ;

但在那之后我有点卡住了。

除非我弄错了,否则你不能同时做这两种事情。但是由于它们是相互独立的(一个Something中的Anythings中的nothings之和与它们的顺序无关),所以这并没有多大关系。一个接一个排序就好了。

noThings:

Somethings 中的 Anytings 进行排序
mySomethings.stream().map(Something::getAnythings)
            .forEach(as -> as.sort(Comparator.comparing(Anything::getNoThings)
                                             .reversed()));

根据 AnythingsnoThings 的总和对 Somethings 进行排序:

mySomethings.sort(Comparator.comparing((Something s) -> s.getAnythings().stream()
                                                         .mapToInt(Anything::getNoThings).sum())
                            .reversed());

请注意,这两种排序都会就地修改各自的列表。


正如@Tagir 所指出的,第二个排序将再次为排序中比较的每对 Somethings 计算 Anythings 的总和。如果列表很长,这会非常浪费。相反,您可以先计算地图中的总和,然后再查找值。

Map<Something, Integer> sumsOfThings = mySomethings.stream()
        .collect(Collectors.toMap(s -> s, s -> s.getAnythings().stream()
                                                .mapToInt(Anything::getNoThings).sum()));

mySomethings.sort(Comparator.comparing(sumsOfThings::get).reversed());

最后我在 Something class 中添加了一个额外的方法。

public int getTotalNoThings() {
  return anythings.stream().collect(Collectors.summingInt(Anything::getNoThings));
}

然后我用这个方法按总数排序 noThings (desc)

somethings = somethings.stream()
            .sorted(Comparator.comparing(Something::getTotalNoThings).reversed())
            .collect(Collectors.toList());

然后我使用上面建议的代码(谢谢!)按 Anything 实例排序 noThings

    somethings .stream().map(Something::getAnythings)
            .forEach(as -> as.sort(Comparator.comparing(Anything::getNoThings).reversed()));

再次感谢您的帮助。

其他解决方案的问题是在排序过程中总和不会存储在任何地方,因此在对大输入进行排序时,将为每一行计算总和数次,从而降低性能。另一种解决方案是创建 (something, sum) 的中间对,按总和排序,然后提取一些东西并忘记总和。以下是如何使用 Stream API 和 SimpleImmutableEntry 作为对 class:

List<Something> orderedSomethings = mySomethings.stream()
        .map(smth -> new AbstractMap.SimpleImmutableEntry<>(smth, smth
                .getAnythings().stream()
                .mapToInt(Anything::getNoThings).sum()))
        .sorted(Entry.<Something, Integer>comparingByValue().reversed())
        .map(Entry::getKey)
        .collect(Collectors.toList());

我的免费 StreamEx 库中有一些语法糖可以使代码更简洁:

List<Something> orderedSomethings = StreamEx.of(mySomethings)
        .mapToEntry(smth -> smth
                .getAnythings().stream()
                .mapToInt(Anything::getNoThings).sum())
        .reverseSorted(Entry.comparingByValue())
        .keys().toList();

至于排序Anything里面的东西:其他方案都可以。