Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Average for group no within group - stream

How to compute an average value for group using stream. Below code which I would like to transform to stream solution.

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

Item class:

public class Item {

    private String groupName;
    private Double value;

    // Full-args constructor
    // Getters and setters
}

I tried something like this:

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

But method sums up averages, so not what I expect. If it was possible to revert summing with grouping it may return excepted result.

like image 409
Bartek Avatar asked Mar 05 '23 15:03

Bartek


2 Answers

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

To make it more readable, you can do it in two operations:

long distinct = items.stream().map(Item::getGroupName).distinct().count();
double sums = items.stream().mapToDouble(Item::getValue).sum();

System.out.println(sums / distinct);

You can do it in a single pass, but requires a custom collector...

like image 194
Eugene Avatar answered Mar 16 '23 16:03

Eugene


You want something like:

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

To get result average of a particular group, get the value from the Map:

double averageForA = map.get("A");
like image 40
Nikolas Charalambidis Avatar answered Mar 16 '23 16:03

Nikolas Charalambidis