Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Average specific values from a list within a list using Java stream

Can't figure out how to read out average weight of animals in a list for a specific building.

I've written another method that gets the names of the animals per kind of animal, so I know my persistency is working.

Zoo:

public class Zoo {
    private List<Animal> animals;
    private List<Building> buildings;

    public Zoo() {
        this.animals = new PersistencyController().giveAnimals();
        this.gebouwen = new PersistencyController().giveBuildings();
    }

 public List<Animal> giveAnimalsByKind(String kindName) {
        return animals.stream().filter(animal -> animal.getKind().getName() == kindName).sorted(Comparator.comparing(Animal::getWeight)).collect(Collectors.toList());
    }

    public double giveAvgWeightForAnimalsInBuilding(String buildingName) {
        return animals.stream().collect(Collectors.averagingDouble(Animal::getWeight));
    }
}

Animal:

public class Animal implements Serializable {

    private int nr;
    private String name;
    private double weight;
    private Kind kind;

    public Animal(int nr, String name, double weight, Kind kind) {
        this.nr = nr;
        this.name = name;
        this.weight= weight;
        this.kind= kind;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
}

Building:

public class Building{

    private String naam;
    private int capaciteit;
    private final List<Animal> animals = new ArrayList<>();

    public Building(String naam, int capaciteit) {
        this.naam = naam;
        this.capaciteit = capaciteit;
    }
}

I want to get the average weight of the animals per building, so the idea is I pass a building's name. Every Building object stores a list of animals, and I need the building object because it stores the name of the building, but I can't figure out how to access it using streams. I know that in the code I posted, I'm not actually using buildingName yet, but that's because I can't even get the average weight of the animals normally. weight is stored as a double in Animal and my persistency is working, as I've tested this in other ways. Anyone experienced with streams explaining how to filter by building and get this average weight out would be much appreciated.

like image 245
Zirael Avatar asked Aug 20 '19 16:08

Zirael


People also ask

How can we get an average of values of elements in a stream operation?

IntStream average() method in Java The average() method of the IntStream class in Java returns an OptionalDouble describing the arithmetic mean of elements of this stream, or an empty optional if this stream is empty. It gets the average of the elements of the stream.


1 Answers

If you have the liberty of using Java9, I would suggest you using the flatMapping collector to get this thing done. Here's how it looks.

Map<String, Double> avgWeightByBuildingName = buildings.stream()
    .collect(Collectors.groupingBy(Building::getName,
        Collectors.flatMapping(b -> b.getAnimals().stream(), 
            Collectors.averagingDouble(Animal::getWeight))));

However here's the java8 solution which looks a bit more verbose compared to the former.

Map<String, Double> avgWeightByBuildingName = buildings.stream()
    .flatMap(b -> b.getAnimals().stream()
        .map(a -> new AbstractMap.SimpleEntry<>(b.getName(), a.getWeight())))
    .collect(Collectors.groupingBy(Map.Entry::getKey, 
        Collectors.averagingDouble(Map.Entry::getValue)));

Alternatively consider writing your own flatMapping collector by following this answer. Here's one such implementation that I came up with after looking at the above answer and the JDK 9 source code.

static <T, U, A, R> Collector<T, ?, R> flatMapping(Function<? super T,
            ? extends Stream<? extends U>> mapper, Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return Collector.of(downstream.supplier(), (r, t) -> {
        try (Stream<? extends U> result = mapper.apply(t)) {
            result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
        }
    }, downstream.combiner(), downstream.finisher(), 
            downstream.characteristics().toArray(new Characteristics[0]));
}

Moreover this has a straight-forward migration strategy for newer Java versions as stated in the below comment.

like image 68
Ravindra Ranwala Avatar answered Oct 17 '22 19:10

Ravindra Ranwala