组内组号的平均值 - 流



如何使用流计算组的平均值。在我想转换为流解决方案的代码下面。

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集之前不需要调用containsadd内部调用它无论如何(

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返回空items0

最新更新