2023-02-11

Performing multiple reduction operations using Streams

I'm attempting to Stream a list of Objects to aggregate the int min and int max based on the grouping of several Field attributes.

public class OccupancyEntry {
    private final int entryID;
    private final String attribute1;
    private final String attribute2;
    private final String attribute3;
    private final int min;
    private final int max;

    public OccupancyEntry(int entryID, String attribute1, String attribute2, String attribute3, int min, int max) {
        this.entryID = entryID;
        this.attribute1 = attribute1;
        this.attribute2 = attribute2;
        this.attribute3 = attribute3;
        this.min = min;
        this.max = max;
    }
}

I'd like to map the above Objects to a a list of the following:

public class OccupancyAggregated {

    private final LocalDate date;
    private final String attribute1;
    private final String attribute2;
    private final String attribute3;
    private final int min;
    private final int max;

    public OccupancyAggregated(LocalDate date, String attribute1, String attribute2, String attribute3, int min, int max) {
        this.date = date;
        this.attribute1 = attribute1;
        this.attribute2 = attribute2;
        this.attribute3 = attribute3;
        this.min = min; //these are summed values
        this.max = max; //these are summed values
    }
}

My attempt so far. Through the help of this answer I've been able to group the entries by a set of fields, but using this method I can no longer get the mapping and reducing functions to work.

        OccupancyEntry entry1 = new OccupancyEntry(1, "1", "2", "3", 1, 10);
        OccupancyEntry entry2 = new OccupancyEntry(1, "1", "2", "3", 1, 10);
        OccupancyEntry entry3 = new OccupancyEntry(1, "A", "B", "C", 1, 10);
        
        ArrayList<OccupancyEntry> occupancyEntries = new ArrayList<>();

        occupancyEntries.add(entry1);
        occupancyEntries.add(entry2);
        occupancyEntries.add(entry3);

        LocalDate date = LocalDate.now();
        ArrayList<OccupancyAggregated> aggregated = new ArrayList<>();


        Map<List<String>, List<OccupancyEntry>> collect = occupancyEntries.stream()
                .collect(groupingBy(x -> Arrays.asList(x.getAttribute1(), x.getAttribute2(), x.getAttribute3())));

The sought after output. Where the min and max fields are reduced from from the entirety of the grouped OccupancyEntry list.

ArrayList<OccupancyAggregated> aggregated = new ArrayList<>();

My previous attempts have consisted out of creating multiple streams which reduce either the min field or the max field based on String concatenated attribute fields.


        final Map<String, Integer> minOccupancy = occupancyEntries.stream()
                .collect((groupingBy(OccupancyEntry::getGroupedAttributes,
                        mapping(OccupancyEntry::getMin,
                                reducing(0, Integer::sum)))));

        final Map<String, Integer> maxOccupancy = occupancyEntries.stream()
                .collect((groupingBy(OccupancyEntry::getGroupedAttributes,
                        mapping(OccupancyEntry::getMax,
                                reducing(0, Integer::sum)))));

Afterwards I'd for loop through one of the maps and then start creating a new list with OccupancyAggregated objects and look up the values in both maps to construct it. This seemed much too convoluted.

I've also been trying to perform the reduce operation in one pass using this answer but I'm not sure how to get it to map to the new Class type properly.

occupancyEntries.stream()
               .reduce((x, y) -> new OccupancyAggregated(
                       date,
                       x.getAttribute1(),
                       x.getAttribute2(),
                       x.getAttribute3(),
                       x.getMin() + y.getMin(),
                       x.getMax() + y.getMax()
               ))
               .orElse(new OccupancyAggregated(date, "no data", "no data", "no data", 0, 0));


No comments:

Post a Comment