群组内群组编号的平均值-流

时间:2018-08-14 11:39:22

标签: java list java-8 java-stream

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

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();
}

但是方法总结了平均值,所以不是我所期望的。如果可以通过分组还原求和,则可能会返回例外结果。

4 个答案:

答案 0 :(得分:5)

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);

您可以一次性完成,但需要自定义收集器...

答案 1 :(得分:1)

您想要类似的东西

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");

答案 2 :(得分:1)

另一种方法是使用collect(supplier, accumulator, combiner)。根据{{​​3}}的示例(请参见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)

但是老实说,在没有并行处理的情况下,我希望您使用自己的解决方案使用简单的循环(由于没有必要在集合contains之前调用add,{ {1}}仍在内部调用它

add

答案 3 :(得分:0)

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为空的0返回items