如何使用流计算组的平均值。在我想转换为流解决方案的代码下面。
public static void main(String[] args) {
List<Item> items = Arrays.asList(
new Item("A", 1.0),
new Item("A", 1.0),
new Item("B", 1.0)
);
System.out.println(averageForGroup(items));
}
public static double averageForGroup(List<Item> items) {
Set<String> uniqueGroups = new HashSet<>();
double sum = 0;
for (Item i : items) {
String groupName = i.getGroupName();
if (!uniqueGroups.contains(groupName)) {
uniqueGroups.add(groupName);
}
sum += i.getValue();
}
return sum / uniqueGroups.size();
}
项目类别:
public class Item {
private String groupName;
private Double value;
// Full-args constructor
// Getters and setters
}
我尝试了这样的事情:
public static double averageForGroup2(List<Item> items) {
return items.stream()
.collect(Collectors.groupingBy(
Item::getGroupName,
Collectors.averagingDouble(Item::getValue)) )
.entrySet().stream()
.mapToDouble(entry -> entry.getValue())
.sum();
}
但是方法总结了平均值,所以不是我所期望的。如果可以通过分组恢复求和,则可能会返回例外结果。
double result = items.stream()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
Item::getGroupName,
Collectors.summingDouble(Item::getValue)),
map -> map.values().stream().mapToDouble(Double::doubleValue).sum() / map.size()));
为了使其更具可读性,您可以通过两个操作来完成:
long distinct = items.stream().map(Item::getGroupName).distinct().count();
double sums = items.stream().mapToDouble(Item::getValue).sum();
System.out.println(sums / distinct);
您可以在一次传递中完成此操作,但需要自定义收集器...
你想要这样的东西:
Map<String, Double> map = items.stream() // Stream
.collect(Collectors.groupingBy( // Group to map
Item::getGroupName, // Key is the groupName
Collectors.averagingDouble(Item::getValue))); // Value is the average of values
要获取特定组的结果平均值,请从以下Map
中获取值:
double averageForA = map.get("A");
另一种方法是使用collect(supplier, accumulator, combiner)
。根据官方教程(见class Averager
(中的示例,您可以编写自己的类,这将允许您
- 收集当前总和和唯一名称
- 处理每个 Item 元素以更新总和和唯一名称集
- 在并行处理的情况下组合该类的其他实例。
所以这样的类可以看起来像
class ItemAverager {
Set<String> set = new HashSet();
double sum = 0;
ItemAverager add(Item item) {
set.add(item.getGroupName());
sum += item.getValue();
return this;
}
ItemAverager combine(ItemAverager ia) {
set.addAll(ia.set);
sum += ia.sum;
return this;
}
double average() {
if (set.size() > 0)
return sum / set.size();
else
return 0; //or throw exception
}
}
并且可以像
List<Item> items = Arrays.asList(
new Item("A", 1.0),
new Item("A", 3.0),
new Item("B", 1.0)
);
double avrg = items
.stream()
.collect(ItemAverager::new,
ItemAverager::add,
ItemAverager::combine
).average(); // `collect` will return ItemAverager
// on which we can call average()
System.out.println(avrg); // Output: 2.5
// (since 1+3+1 = 5 and there are only two groups 5/2 = 2.5)
但老实说,在没有并行处理的情况下,我更喜欢使用简单循环的您自己的解决方案(可能没有什么改进,因为您在add
集之前不需要调用contains
,add
内部调用它无论如何(
public static double averageForGroup(List<Item> items) {
Set<String> uniqueGroups = new HashSet<>();
double sum = 0;
for (Item item : items) {
uniqueGroups.add(item.getGroupName());
sum += item.getValue();
}
return sum / uniqueGroups.size();
}
public static double getAverageByGroups(List<Item> items) {
Map<String, Double> map = Optional.ofNullable(items).orElse(Collections.emptyList()).stream()
.collect(Collectors.groupingBy(Item::getGroupName, Collectors.summingDouble(Item::getValue)));
return map.isEmpty() ? 0 : map.values().stream().mapToDouble(value -> value).sum() / map.size();
}
在此示例中,getAverageByGroups
返回空items
的0
。