我当前的代码:
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
有所有的getter和setter。
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)
这是我的全部代码。
List<Cause> sources = new CopyOnWriteArrayList<>();
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,只需将每个值相加即可。
注意你的两个陈述
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
步骤意味着缓冲所有对象以进行排序(通常在数组中),因此我将排序移到了整理器函数中,该函数无论如何都必须处理列表(包装数组)。
可以在不收集和排序所有元素的情况下解决任务,例如参见这个答案。然而,只有当组的数量明显大于您的限制时,这种优化才会得到回报,即不在您从四个中选择三个的示例中。