Stream group incoming sort events

Viewed 94

I have a series of events, each event contains a timestamp, device, serialnumber, and measurement.

class Event {
    private String device;
    private String description;
    private String serialnumber;
    private Measurement measurement;
}

class Measurement {
    private LocalDateTime timestamp;
    private int value;
}

I have a stream of these events, and I would like to aggregate them into a simpler structure, dropping the serial number, then group them by device, and then sorting the measurements by timestamp and value.

{device: "device_1", description: "first device", serialnumber: "1", measurement: { timestamp: 2022-04-23T18:20:22Z, value: 180}}
{device: "device_2", description: "second device", serialnumber: "2", measurement: { timestamp: 2022-04-23T18:20:28Z, value: 120}}
{device: "device_2", description: "second device", serialnumber: "2", measurement: { timestamp: 2022-04-23T18:20:20Z, value: 160}}
{device: "device_1", description: "first device", serialnumber: "1", measurement: { timestamp: 2022-04-23T18:20:22Z, value: 170}}
[
    {
        device: "device_1",
        description: "first device",
        measurements: [
            { timestamp: 2022-04-23T18:20:22Z, value: 170},
            { timestamp: 2022-04-23T18:20:22Z, value: 180}
        ]
    },
    {
        device: "device_2",
        description: "second device",
        measurements: [
            { timestamp: 2022-04-23T18:20:20Z, value: 160},
            { timestamp: 2022-04-23T18:20:28Z, value: 120}
        ]
    }
]

I've managed to get the required format by creating a "builder" class in which you can insert events that get then processed and added to the data members in the correct format/order. However, I think it would be nicer to somehow achieve this on the fly without another extra class, but using the groupingBy and toMap (and other?) stream methods.

2 Answers

You could get a Map<String, List<Measurement>> using groupingBy if that is what you are looking for:

Map<String, List<Measurement>> result = 
        events.stream()
                .collect(Collectors.groupingBy(Event::getDevice,
                         Collectors.mapping(Event::getMeasurement, Collectors.toList())));

I've come up with this solution assuming that for each device there cannot be multiple measurements with same timestamp and value.

I understand that this could be a limit, but since I cannot see any ways of sorting a subset collected with a Collectors, I've decided to use a TreeSet to store the grouped measurements so that they will be sorted according to their natural ordering.

class Event {
    private String device;
    private String description;
    private String serialnumber;
    private Measurement measurement;

    public Event(String device, String description, String serialnumber, Measurement measurement) {
        this.device = device;
        this.description = description;
        this.serialnumber = serialnumber;
        this.measurement = measurement;
    }

    //... getters and setters ...
}

class Measurement implements Comparable<Measurement> {
    private LocalDateTime timestamp;
    private int value;

    public Measurement(LocalDateTime timestamp, int value) {
        this.timestamp = timestamp;
        this.value = value;
    }

    //... getters and setters ...

    //Redefinition of the natural ordering by timestamp and value

    @Override
    public int compareTo(Measurement m) {
        Comparator<Measurement> cmp = Comparator.comparing(Measurement::getTimestamp).thenComparing(Measurement::getValue);
        return cmp.compare(this, m);
    }

    public String toString(){
        return String.format("%s - %d", timestamp, value);
    }
}

public class Test {

    public static void main(String[] args) {
        List<Event> dataSource = new ArrayList<>(List.of(
                new Event("device_1", "first device", "1", new Measurement(LocalDateTime.parse("2022-04-23T18:20:22"), 180)),
                new Event("device_2", "second device", "2", new Measurement(LocalDateTime.parse("2022-04-23T18:20:28"), 120)),
                new Event("device_2", "second device", "2", new Measurement(LocalDateTime.parse("2022-04-23T18:20:20"), 160)),
                new Event("device_1", "first device", "1", new Measurement(LocalDateTime.parse("2022-04-23T18:20:22"), 170))));

        Map<String, TreeSet<Measurement>> map = dataSource.stream()
                .collect(Collectors.groupingBy(Event::getDevice, Collectors.mapping(Event::getMeasurement, Collectors.toCollection(TreeSet::new))));

        for (String key: map.keySet()){
            System.out.printf("%s => %s%n", key, map.get(key));
        }
    }
}
Related