Java 集合:为包含相等字符串的每个匹配对象获取类列表中 double / float 的总和

问题描述 投票:0回答:1

我当前的代码:

List<Float> totalAmounts = sources.stream().collect(Collectors.groupingBy(Cause::getAmount, Collectors.counting()))
                .entrySet().stream().sorted(entryComp).map(Map.Entry::getValue).limit(3).toList();

这个想法是,它将所有

Cause.getAmount()
(即
float
)添加到最多 3 个元素的列表中
Cause
类构造函数如下所示,
Cause
拥有所有 Getters 和 Setters。

public Cause(String name, int time, float amount) {}

例如,

sources
列表看起来像

new Cause("Name", 34, 15.53F);
new Cause("Name", 636, 2.12F);
new Cause("Name", 2345, 3.14F);
new Cause("Name", 568, 9F);
new Cause("OtherName", 10, 5.55F);
new Cause("OtherName", 5324, 0.27F);
new Cause("OtherName", 1, 6.21F);
new Cause("RealName", 921, 7.05F);
new Cause("RealName", 3899, 11.11F);
new Cause("RealName", 1782, 8.38F);
new Cause("BadName", 234, 1.01F);
new Cause("BadName", 581, 6.31F);

以及

totalAmounts
的预期结果作为完整的
<K, V>
将是

(4, 29.79F)
(3, 12.03F)
(3, 26.54F)

(我知道我只是拉

Entry::getValue
Key
是相等字符串的数量
Value
是具有相同字符串最后一个构造函数参数的所有对象的总和(它必须是 double 或 float)

我几乎没有处理过 Collections 或 Streams,我在这个问题中迷失了。

编辑:这是我的整个代码。


       private String topThree() {
        StringBuilder result = new StringBuilder();
        Comparator<Map.Entry<String, Long>> entryComparator = Collections.reverseOrder(Map.Entry.comparingByValue());
        Comparator<Map.Entry<Float, Double>> entryComp = Collections.reverseOrder(Map.Entry.comparingByValue());

        List<String> most = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting()))
                .entrySet().stream().sorted(entryComparator).map(Map.Entry::getKey).limit(3).toList();

        List<Long> mostTimes = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting()))
                .entrySet().stream().sorted(entryComparator).map(Map.Entry::getValue).limit(3).toList();

        List<Float> total = sources.stream().collect(Collectors.groupingBy(Cause::getAmount, Collectors.counting()))
                .entrySet().stream().sorted(entryComp).map(Map.Entry::getValue).limit(3).toList();

        for (int i = 0; i < most.size(); i++) {
            try {
                result.append((i+1) + ". " + most.get(i) + " " + mostTimes.get(i) + " " + total.get(i) + "\n");
            } catch (ArrayIndexOutOfBoundsException ignored) {}
        }
        return result.toString();
    }
    


        @Getter @Setter
    private static final class Cause {
        private Source source;
        private String name;
        private float amount;
        private int tickRecordedOn;
        public Cause(Source source, String name, float amount, int tickRecordedOn) {
            this.source = source;
            this.name = name;
            this.amount = amount;
            this.tickRecordedOn = tickRecordedOn;// Used for syncing, nothing else
        } 
    }

topThree
方法背后的想法是返回

(Cause Name) (Amount of times the Cause name is in the list) (The sum of the Cause amount)

基本上将 Cause 列表压缩为单个字符串,即 Cause,只需将每个值相加即可。

collections java-8 stream
1个回答
0
投票

注意你的两个陈述

List<String> most = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting()))
        .entrySet().stream().sorted(entryComparator).map(Map.Entry::getKey).limit(3).toList();

List<Long> mostTimes = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting()))
        .entrySet().stream().sorted(entryComparator).map(Map.Entry::getValue).limit(3).toList();

执行相同(几乎)的操作两次,将另一个结果所需的信息删除到

.map(Map.Entry::getKey)
中。
.map(Map.Entry::getValue)
步骤。

因此,删除此

map
步骤并将信息收集到包含所有信息的结果中,例如

Map<String,Long> mostTimes = sources.stream()
    .collect(Collectors.groupingBy(Cause::getName, Collectors.counting()))
    .entrySet().stream()
    .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).limit(3)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

此地图不会排序,但包含前三个元素。

现在,要对缺失的金额求和,请将

Collectors.counting()
更改为
Collectors.summarizingDouble(Cause::getAmount)
。由于这会将结果值从
Long
更改为
DoubleSummaryStatistics
,因此还需要更改比较器:

Map<String, DoubleSummaryStatistics> mostTimes = sources.stream()
    .collect(Collectors.groupingBy(Cause::getName,
            Collectors.summarizingDouble(Cause::getAmount)))
    .entrySet().stream()
    .sorted(Collections.reverseOrder(Map.Entry.comparingByValue(
            Comparator.comparingDouble(DoubleSummaryStatistics::getSum)))).limit(3)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

由于预期的最终结果是

String
,因此一个选择是更改收集器以首先创建
String
。这也将尊重
sorted
步骤建立的顺序:

private String topThree() {
    return sources.stream()
        .collect(Collectors.groupingBy(Cause::getName,
            Collectors.summarizingDouble(Cause::getAmount)))
        .entrySet().stream()
        .sorted(Collections.reverseOrder(Map.Entry.comparingByValue(
            Comparator.comparingDouble(DoubleSummaryStatistics::getSum)))).limit(3)
        .map(entry -> entry.getKey()
                      + " " + entry.getValue().getCount()
                      + " " + entry.getValue().getSum())
        .collect(Collectors.joining("\n"));
}

这将产生

Name 4 29.78999972343445
RealName 3 26.539999961853027
OtherName 3 12.030000239610672

如果你可以在没有前面的排名的情况下生活,这可能是一个可以接受的结果。

否则,解决方案将涉及更多的手动处理:

private String topThree() {
    return sources.stream()
        .collect(Collectors.groupingBy(Cause::getName,
            Collectors.summarizingDouble(Cause::getAmount)))
        .entrySet().stream()
        .collect(Collectors.collectingAndThen(
            Collectors.toCollection(ArrayList::new), list -> {
            list.sort(Map.Entry.comparingByValue(
                Comparator.comparingDouble(DoubleSummaryStatistics::getSum)));
            StringBuilder result = new StringBuilder();
            for(int rank = 1, i = list.size()-1; i >= 0 && rank <= 3; rank++,i--) {
                result.append(rank).append(". ").append(list.get(i).getKey())
                    .append(" ").append(list.get(i).getValue().getCount())
                    .append(" ")
                    .append(list.get(i).getValue().getSum()).append("\n");
            }
            return result.toString();
        }));
}

这将产生

1. Name 4 29.78999972343445
2. RealName 3 26.539999961853027
3. OtherName 3 12.030000239610672

由于

sorted
步骤意味着缓冲所有对象以进行排序(通常在数组中),因此我将排序移到了整理器函数中,该函数无论如何都必须处理列表(包装数组)。

可以在不收集和排序所有元素的情况下解决任务,例如参见这个答案。然而,只有当组的数量明显大于您的限制时,这种优化才会得到回报,即不在您从四个中选择三个的示例中。

© www.soinside.com 2019 - 2024. All rights reserved.