Angenommen, ich habe eine Gruppe von Autoscootern, die an ihren Seiten eine Größe, eine Farbe und eine Kennung ("Autocode") haben.

class BumperCar {
    int size;
    String color;
    String carCode;
}

Jetzt muss ich die Autoscooter einem List von DistGroup Objekten zuordnen, die jeweils die Eigenschaften size, color und List von Fahrzeugcodes enthalten.

class DistGroup {
    int size;
    Color color;
    List<String> carCodes;

    void addCarCodes(List<String> carCodes) {
        this.carCodes.addAll(carCodes);
    }
}

Beispielsweise,

[
    BumperCar(size=3, color=yellow, carCode=Q4M),
    BumperCar(size=3, color=yellow, carCode=T5A),
    BumperCar(size=3, color=red, carCode=6NR)
]

Sollte führen zu:

[
    DistGroup(size=3, color=yellow, carCodes=[ Q4M, T5A ]),
    DistGroup(size=3, color=red, carCodes=[ 6NR ])
]

Ich habe Folgendes versucht, das tatsächlich das tut, was ich möchte. Aber das Problem ist, dass es das unmittelbare Ergebnis (in ein Map) materialisiert und ich denke auch, dass es sofort gemacht werden kann (vielleicht mit mapping oder collectingAndThen oder reducing oder so), was zu einem eleganteren Code führt.

List<BumperCar> bumperCars = ...
Map<SizeColorCombination, List<BumperCar>> map = bumperCars.stream()
    .collect(groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())));

List<DistGroup> distGroups = map.entrySet().stream()
    .map(t -> {
        DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
        d.addCarCodes(t.getValue().stream()
            .map(BumperCar::getCarCode)
            .collect(toList()));
        return d;
    })
    .collect(toList());

Wie kann ich das gewünschte Ergebnis erzielen, ohne eine Variable für ein sofortiges Ergebnis zu verwenden?

Bearbeiten: Wie kann ich das gewünschte Ergebnis erzielen, ohne das unmittelbare Ergebnis zu erzielen? Ich suche nur nach einem Weg, der das unmittelbare Ergebnis nicht verwirklicht, zumindest nicht an der Oberfläche. Das heißt, ich bevorzuge es, so etwas nicht zu verwenden:

something.stream()
    .collect(...) // Materializing
    .stream()
    .collect(...); // Materializing second time

Natürlich, wenn das möglich ist.


Beachten Sie, dass ich der Kürze halber Getter und Konstruktoren weggelassen habe. Sie können auch davon ausgehen, dass die Methoden equals und hashCode ordnungsgemäß implementiert sind. Beachten Sie auch, dass ich das SizeColorCombination verwende, das ich als Gruppierungsschlüssel verwende. Diese Klasse enthält offensichtlich die Eigenschaften size und color. Klassen wie Tuple oder Pair oder eine andere Klasse, die eine Kombination aus zwei beliebigen Werten darstellt, können ebenfalls verwendet werden.
Bearbeiten : Beachten Sie auch, dass stattdessen eine alte Skool for-Schleife verwendet werden kann, die jedoch nicht in den Geltungsbereich dieser Frage fällt.

19
MC Emperor 18 Jän. 2019 im 15:29

4 Antworten

Beste Antwort

Wenn wir annehmen, dass DistGroup hashCode/equals basierend auf size und color hat, können Sie dies folgendermaßen tun:

bumperCars
    .stream()
    .map(x -> {
        List<String> list = new ArrayList<>();
        list.add(x.getCarCode());
        return new SimpleEntry<>(x, list);
    })
    .map(x -> new DistGroup(x.getKey().getSize(), x.getKey().getColor(), x.getValue()))
    .collect(Collectors.toMap(
        Function.identity(),
        Function.identity(),
        (left, right) -> {
            left.getCarCodes().addAll(right.getCarCodes());
            return left;
        }))
    .values(); // Collection<DistGroup>
6
Naman 18 Jän. 2019 im 13:04

Lösung-1

Einfach die beiden Schritte zu einem zusammenführen:

List<DistGroup> distGroups = bumperCars.stream()
        .collect(Collectors.groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())))
        .entrySet().stream()
        .map(t -> {
            DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
            d.addCarCodes(t.getValue().stream().map(BumperCar::getCarCode).collect(Collectors.toList()));
            return d;
        })
        .collect(Collectors.toList());

Lösung-2

Ihre Zwischenvariable wäre viel besser, wenn Sie groupingBy zweimal mit beiden Attributen verwenden und die Werte als List von Codes zuordnen könnten, etwa:

Map<Integer, Map<String, List<String>>> sizeGroupedData = bumperCars.stream()
        .collect(Collectors.groupingBy(BumperCar::getSize,
                Collectors.groupingBy(BumperCar::getColor,
                        Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))));

Und verwenden Sie einfach forEach, um der endgültigen Liste Folgendes hinzuzufügen:

List<DistGroup> distGroups = new ArrayList<>();
sizeGroupedData.forEach((size, colorGrouped) ->
        colorGrouped.forEach((color, carCodes) -> distGroups.add(new DistGroup(size, color, carCodes))));

Hinweis : Ich habe Ihren Konstruktor so aktualisiert, dass er die Liste der Kartencodes akzeptiert.

DistGroup(int size, String color, List<String> carCodes) {
    this.size = size;
    this.color = color;
    addCarCodes(carCodes);
}

Weitere Kombination der zweiten Lösung zu einer vollständigen Aussage (obwohl ich selbst die forEach ehrlich favorisieren würde):

List<DistGroup> distGroups = bumperCars.stream()
        .collect(Collectors.groupingBy(BumperCar::getSize,
                Collectors.groupingBy(BumperCar::getColor,
                        Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))))
        .entrySet()
        .stream()
        .flatMap(a -> a.getValue().entrySet()
                .stream().map(b -> new DistGroup(a.getKey(), b.getKey(), b.getValue())))
        .collect(Collectors.toList());
2
Naman 18 Jän. 2019 im 13:18

Sie können mithilfe von BiConsumer sammeln, die (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) als Parameter verwenden

Collection<DistGroup> values = bumperCars.stream()
        .collect(HashMap::new, (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) -> {
                SizeColorCombination dg = new SizeColorCombination(bc.color, bc.size);
                DistGroup distGroup = res.get(dg);
                if(distGroup != null) {
                    distGroup.addCarCode(bc.carCode);
                }else {
                    List<String> codes = new ArrayList();
                    distGroup = new DistGroup(bc.size, bc.color, codes);
                    res.put(dg, distGroup);
                }
                },HashMap::putAll).values();
0
SEY_91 18 Jän. 2019 im 15:00

Überprüfen Sie meine Bibliothek AbacusUtil:

StreamEx.of(bumperCars)
         .groupBy(c -> Tuple.of(c.getSize(), c.getColor()), BumperCar::getCarCode)
         .map(e -> new DistGroup(e.getKey()._1, e.getKey()._2, e.getValue())
         .toList();
0
123-xyz 22 Jän. 2019 im 16:11