Collections Streams - Hilfestellung bei komplexer Struktur

Bela B.

Aktives Mitglied
Hallo zusammen,

ich bin gerade dabei, mir Streams beizubringen. Nachdem ich Lambdas nun verstanden und selbstständig anwenden und die Vorteile erkennen kann, hadere ich gerade dabei mit der richtigen Verwendung von Streams.

In meinem aktuellen Fall habe ich folgende Situtation:
Klasse MeasurementPoint, beschreibt einen Messpunkt in meinen Daten
Java:
public class MeasurementPoint {
    private final LocalDateTime timestamp;
    private final Double temperature;

    public MeasurementPoint(LocalDateTime timestamp, Double temperature) {
        this.timestamp = timestamp;
        this.temperature = temperature;
    }

    public LocalDateTime getTimestamp() {
        return timestamp;
    }

    public Double getTemperature() {
        return temperature;
    }

    @Override
    public String toString() {
        return "MeasurementPoint{" +
                "timestamp=" + timestamp +
                ", temperature=" + temperature +
                '}';
    }
}

Klasse ChannelPoint, beschreibt einen Messlauf in meinen Daten, ein Kanalpunkt besteht also aus mehreren Measurementpoints, je nach Anzahl der Messfühler:
Java:
public class ChannelPoint {
    private final LocalDateTime timestamp;
    private final List<MeasurementPoint> measurementPoints;

    public ChannelPoint(LocalDateTime timestamp, List<MeasurementPoint> measurementPoints) {
        this.timestamp = timestamp;
        this.measurementPoints = measurementPoints;
    }

    public Double getTemperatureMax() {
        Optional<MeasurementPoint> result = measurementPoints.stream()
                .max(Comparator.comparing(MeasurementPoint::getTemperature));
        return result.isPresent() ? result.get().getTemperature() : 0.0;
    }

    public Double getTemperatureMin() {
        Optional<MeasurementPoint> result = measurementPoints.stream()
                .min(Comparator.comparing(MeasurementPoint::getTemperature));

        return result.isPresent() ? result.get().getTemperature() : 0.0;
    }

    public List<MeasurementPoint> getMeasurementPoints() {
        return measurementPoints;
    }

    @Override
    public String toString() {
        return "ChannelPoint{" +
                "timestamp=" + timestamp +
                ", measurementPoints=" + measurementPoints +
                '}';
    }
}

Dazu habe ich dann jetzt einen Check, der auf den eingelesenen ChannelPoints arbeiten soll.
Bei diesem speziellen Check geht es darum, dass die Temperatur für einen Zeitraum t zwischen den Temperaturen Tmin und Tmax sich befinden muss.

Ich hätte das jetzt auf zwei vom Interface Check implementierenden Klassen gelöst. Der erste Check überprüft also nur, welche ChannelPoints zwischen Tmin und Tmax liegen.

TempMinMaxCheck:
Java:
public class TempMinMaxCheck implements Check{

    private final Profile profile;
    private final String checkName;
    private final Double tempMin;
    private final Double tempMax;
    private final Duration duration;

    private ChannelPoint start;
    private ChannelPoint end;

    public TempMinMaxCheck(Profile profile, String checkName, Double tempMin, Double tempMax, Duration duration) {
        this.profile = profile;
        this.checkName = checkName;

        this.tempMin = tempMin;
        this.tempMax = tempMax;
        this.duration = duration;
    }

    @Override
    public boolean execute() {
        // ---- hier soll dann über Stream überprüft werden, welche Punkte in dem Temperaturbereich liegen, gleichzeitig soll hier dann ChannelPoint start und ChannelPoint end gesetzt werden. start ist der erste ChannelPoint, bei dem alle MeasurementPoints größergleich Tmin sind, end entsprechend der ChannelPoint, bei dem mindestens ein MeasurementPoint vom ChannelPoint größer Tmax oder aber kleiner Tmin ist.
        );

        return false;
    }

    @Override
    public String getCheckName() {
        return null;
    }

    @Override
    public ChannelPoint getStart() {
        return null;
    }

    @Override
    public ChannelPoint getEnd() {
        return null;
    }
}

Dazu das Interface Check:
Java:
public interface Check {
    boolean execute();
    String getCheckName();
    ChannelPoint getStart();
    ChannelPoint getEnd();
}

Nur wie müsste ich denn hier jetzt an der gekennzeichneten Quellcode-Stelle den Stream schreiben? Lösen könnte ich das über eine ForEach und if Kaskade, denke aber, dass soetwas ja prädestiniert für Streams ist und damit dann auch deutlich schöner zu lesen ist.
 
Das Problem hierbei ist, dass du ja nicht einfach deine Temperaturenliste filtern möchtest, sondern einen Zustand aufrechterhalten willst. Wenn wir mal einfache Zahlen als Beispiel nehmen, willst du ja z.B. nicht aus der Liste [1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 5] einfach alle Zahlen zwischen 2 und 5 haben, sondern den ersten Bereich, in dem sich die Zahlen zwischen 2 und 5 bewegen.
Hierfür sind Streams nicht wirklich elegant.
Eine Lösung, die mir da einfallen würde, die einfach ganz generisch den ersten Bereich (die Indizes) aus einer Liste von T <: Comparable liefert, der in einem definierten Bereich min <= max liegt, wäre z.B.:
Java:
public class Ranges {
    public static <T extends Comparable<T>> int[] findIndicesOfFirstRangeBetweenMinMax(
            T min, T max, java.util.List<T> s) {
        return java.util.stream.IntStream.
         range(0, s.size())
        .dropWhile(i -> s.get(i).compareTo(min) < 0)
        .takeWhile(i -> s.get(i).compareTo(min) >= 0 && s.get(i).compareTo(max) <= 0)
        .boxed()
        .reduce((int[]) null,
                (pair, e) ->
                    pair == null
                    ? new int[] {e, e}
                    : new int[] {pair[0], e},
                (a, b) -> null);
    }
    public static void main(String[] args) {
        System.out.println(java.util.Arrays.toString(
                findIndicesOfFirstRangeBetweenMinMax(
                        3, 6,
                        java.util.List.of(1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9))));
    }
}
 
Danke @httpdigest , ich schaue mir deinen Vorschlag mal im Detail an. Muss mir mal ein gutes Tutorial bezüglich Streams suchen und die Doku durchackern, denn mir wäre deine Lösung so auch nie eingefallen (kenne so gut wie keine Methoden, die du da aufrust, also z.B. dropWhile, boxed, ...).

Wenn ihr da Vorschläge für Tutorials habt, gerne her damit.
 
Wenn ihr da Vorschläge für Tutorials habt, gerne her damit.

Schau doch einfach in die Doku: https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html
und experimentiere etwas damit herum.

Ich hatte früher auch immer ziemlich Respekt vor der Doku, aber sie ist einfach super, da man über die Links sehr schnell zwischen den einzelnen Typen (Rückgabetypen oder Parametertypen) und Hinweisen hin- und herspringen kann.

Sobald man sich daran gewöhnt hat damit zu arbeiten, geht der erste Blick immer dahin.
 
Zuletzt bearbeitet:
Ich persönlich würde dafür keine Streams nutzen, das lässt sich ohne deutlich schöner und eleganter lösen. Etwas so in die Richtung würde mir vorschweben:

Java:
import java.util.*;
import java.util.function.*;


public class IntervalCollector<T> {

    private final Predicate<T> check;

    private final List<Deque<T>> sets = new ArrayList<>();

    private Deque<T> current = null;


    public IntervalCollector(final Predicate<T> check) {
        this.check = check;
    }


    public void add(T point) {
        if (!check.test(point)) {
            current = null;
            return;
        }

        if (current == null) {
            current = new ArrayDeque<>();
            sets.add(current);
        }

        current.add(point);
    }

    public static void main(String[] args) {
        var ints = Arrays.asList(1, 2, 1, 6, 2, 2, 5, 7, 8, 2, 6, 2, 4, 7, 8, 4, 2);
        var collector = new IntervalCollector<Integer>((i) -> i > 2 && i < 8);
        ints.forEach(collector::add);
        System.out.println(collector.sets);
    }

}
 
Danke auch dir @mrBrown

Da ich in Streams wie gesagt noch nicht fit bin, fehlt mir auch noch der Blick dafür, wo sie Sinn machen.
Nach meinen ersten Gehversuchen damit, erschien mir mein Fall halt mit Streams elegant lösbar, ich weiß/wusste nur nicht wie.

Deque kannte ich bisher auch nicht. Ich denke, ich muss mir da nochmal einiges draufpacken, was die "neueren" Features betrifft.
 
Muss jetzt nochmal blöd nachfragen, gehe gerade den Code von @mrBrown durch, verstehe aber ehrlich gesagt die Zeile private final List<Deque<T>> sets = new ArrayList<>(); nicht.

Warum ist denn hier eine List in einer List (Deque ist ja quasi auch sowas wie eine Liste und laut mrBrown und auch meinem Verständnis, brauche ich kein Deque).

Wofür genau brauche ich hier current und sets als List?
 
Muss jetzt nochmal blöd nachfragen, gehe gerade den Code von @mrBrown durch, verstehe aber ehrlich gesagt die Zeile private final List<Deque<T>> sets = new ArrayList<>(); nicht.

Warum ist denn hier eine List in einer List (Deque ist ja quasi auch sowas wie eine Liste und laut mrBrown und auch meinem Verständnis, brauche ich kein Deque).

Wofür genau brauche ich hier current und sets als List?
Der Code sucht nicht nur die erste, sondern alle Sublisten, für die die Bedingung zutrifft – deine Anforderungen klangen zumindest danach.

Für [2,3,4,3,2,5,6,5,2] und der Bedingen 2 < x < 7 würden [3,4,3] und [5,6,5] gefunden werden, daher eine Liste von Listen.
 

Neue Themen


Zurück
Oben