Dreidimensionales Array filtern

Hallo zusammen,

folgende Aufgabe stellt sich mir:
Ein dreidimensionalen Array, welches Objekte speichert soll nach bestimmten Kriterien gefiltert werden.
Ein bestimmter Wert der enthaltenen Objekte soll addiert werden wenn das Filterkriterium mit einer bestimmten Eigenschaft des Objekts übereinstimmt.

Lösungsideen:
Für eine ähnliche Aufgabenstellung bei Verwendung eines eindimensionales Array konnte ich das Problem mit Lambda-Expressions lösen.

    public int getAnzahlSchraubenGesamt(Karton karton[], int laenge)
    {
        int anzahlGes = Arrays.stream(karton)
            .filter( a -> (a != null) && ( a.getLaenge() == laenge ))    
                .mapToInt(a -> a.getAnzahlSchrauben()).sum();
        return anzahlSchraubenGes;
    }

Jetzt muss diese Art Filter auf ein dreidimensionales Array angewendet werden.
Leider habe ich keine Ahnung wie ich dies mit Hilfe von Lambda-Expressions lösen könnte.
Auch nach längerer Suche im Internet fand ich keine Hinweise, die mir bei der Problemlösung helfen konnten.

Ich bin am Überlegen, ob ich das Problem nicht einfach mit verschachtelten Schleifendurchläufen lösen sollte, ganz ohne Lambda.
Allerdings bin ich mir nicht sicher ob dies nicht eleganter, schlanker und performanter geht.

Für Anregungen, Ideen und Hilfen wäre ich der Community sehr dankbar.

Gruß
Byte

ob stream + filter + mapToInt + sum gegenüber einer Schleife mit if und += eleganter ist stellt sich als Frage schon bei 1D, aber bitte,
Performance ist davon nebenei gewiss nicht zu erwarten, aber mag zurückstehen und im Nanobereich eh egal

zwei Strategien fallen mir für dich ein:

  • ein 2D-Array als Stream von 1D-Arrays durchlaufen und für jedes dieser 1D-Array die bisherige Methode getAnzahlSchraubenGesamt() aufrufen
    (.mapToInt(oneDArray -> getAnzahlSchraubenGesamt(oneDArray , laenge))?), summieren
  • ein 2D-Array in einen zigfach längeren 1D-Stream umwandeln, gewiss Standardfunktion oder für sich eine Aufgabe, danach dann wie bisher summieren

für höhere D jeweils entsprechend weiter zu denken


so ist mal wieder eine Kinderübung für fast ersten Tag Java in komplizierte Frage komplizierter Syntax + API-Methoden verwandelt :wink:

Also eigentlich ist ein mehrdimensionales Array in Java nur ein “Array od Arrays”.

Die naheliegende Lösung wäre also, einfach Array.stream() zu kaskadieren.

etwa so: (ungetestet):int anzahlGes = Arrays.stream(Arrays.stream(Arrays.stream(karton))) .filter( a -> (a != null) && ( a.getLaenge() == laenge )) .mapToInt(a -> a.getAnzahlSchrauben()).sum();
bye
TT

How to Loop and Print 2D array using Java 8 - Stack Overflow
hatte ich gerade gefunden,
Stream.of(karton).flatMap(Stream::of) usw. ist es wohl, Arrays.stream() geschachtelt geht weniger

das erste Stream.of() dürfte mit Arrays.stream() für Objekte-Array äquivalent sein

Vielen Dank Ihr zwei.
Der Tipp mit der Kaskadenlösung ist irgendwie einleuchtend - funktioniert aber leider nicht.
Die 3D-2D-1D-Lambda-Lösung ist mir nicht einleuchtend.
Daher werde ich wohl den konventionellen Lösungsansatz mit den verschachtelten Schleifen wählen.

Gruß
Byte

so schön auch zu hören ist das einfache Aufgeben ja doch nicht wünschenswert,
was funktioniert nicht?

soweit an Information gegeben, also das Karton-Beispiel, dürfte es gehen:

    public static void main(String[] args)  {
        Karton[][][] ka = new Karton[2][3][4];
        for (int i = 0; i < ka.length; i++)
            for (int j = 0; j < ka**.length; j++)
                for (int k = 0; k < ka**[j].length; k++)
                    ka**[j][k] = new Karton();

        System.out.println(getAnzahlSchraubenGesamt(ka, 1));
    }

    public static int getAnzahlSchraubenGesamt(Karton[][][] karton, int laenge)  {
        int anzahlGes = Arrays.stream(karton).flatMap(Stream::of).flatMap(Stream::of)
                              .filter(a -> (a != null) && (a.getLaenge() == laenge)).mapToInt(a -> a.getAnzahlSchrauben()).sum();
        return anzahlGes;
    }
}

class Karton {
    int getLaenge()  {
        return 1;
    }

    int getAnzahlSchrauben() {
        return 5;
    }
}

Ausgabe 120

nebenbei interessant passende Frage: kann man so ein Array, auch 1D, streamend befüllen?
1D geht wohl noch ganz gut:
functional programming - Java 8 fill array with supplier - Stack Overflow

ab 2D sieht es schwieriger aus:
Initializing 2d array with streams in Java - Stack Overflow
oder gibt es noch einfacheres?

ein Grund mehr bei der Schleife zu bleiben

Das ist dann aber nicht Java 8-konform und eine beliebige IDE könnte es anmerken, Lambda/funktional/streams/usw. zu benutzen.

Edit: Vielleicht noch ein Lernlink für Fragestellung des TOs: Lambda, Stream, Filter-Map-Reduce

[quote=SlaterB]ab 2D sieht es schwieriger aus:
Initializing 2d array with streams in Java - Stack Overflow[/quote]Die lösung ließe sich doch recht simpel verallgemeinern. Die return-Zeile wird halt mit jeder Dimension immer länger…

Andererseits sind die geschachtelten Loops nun auch nicht sooo hässlich…

bye
TT

@SlaterB :
Recht herzlichen Dank für deine Mühe!
Dein Lösungsvorschlag funktioniert wunderbar.
Mir ist ein peinlicher Schnitzer unterlaufen.
Ich vergaß “java.util.stream.Stream” zu importieren.

@CyborgBeta :
Auch dir danke ich sehr für die von dir veröffentlichten Links.

Was sind denn die Dimensionen dieses Arrays? Den Klassennamen nach könnten es räumlich gestapelte Kartons sein, im Sinne von

int x = 1;
int y = 6;
int z = 8;
Karton k = kartons[x][y][z];

Andernfalls klingt ein 3D-Array von Referenzen ist schon sehr suspekt … also, es klingt nach etwas, was je nach Ziel und Nutzungs- und Anwendungsfall vielleicht hinter einem

interface Boxes {
    Karton get(int x, int y, int z);

    // oder auch, mit geeigneter "Point3D"-Klasse
    Karton get(Point3D coordinates);

    Stream<Karton> getStream();
}

versteckt werden könnte.

Ansonsten ist es wohl nicht verkehrt, das “Flachklopfen” als dedizierten Schritt rauszuzuiehen

package bytewelt;

import java.util.Arrays;
import java.util.stream.Stream;

public class ArrayStream3D
{
    public static void main(String[] args)
    {
        Karton[][][] ka = new Karton[2][3][4];
        for (int i = 0; i < ka.length; i++)
            for (int j = 0; j < ka**.length; j++)
                for (int k = 0; k < ka**[j].length; k++)
                    ka**[j][k] = new Karton();

        System.out.println(getAnzahlSchraubenGesamt(ka, 1));
    }

    // Praktisch genau die Methode, die du ursprünglich hattest, nur wird
    // direkt der Stream übergeben:
    private static int getAnzahlSchraubenGesamt(
        Stream<? extends Karton> kartons, int laenge)
    {
        return kartons
            .filter(a -> (a != null) && (a.getLaenge() == laenge))
            .mapToInt(a -> a.getAnzahlSchrauben()).sum();
    }

    private static int getAnzahlSchraubenGesamt(Karton[][][] karton, int laenge)
    {
        return getAnzahlSchraubenGesamt(flatten(karton), laenge);
    }

    private static <T> Stream<T> flatten(T ts[][][])
    {
        return Arrays.stream(ts).flatMap(t -> flatten(t));
    }

    private static <T> Stream<T> flatten(T ts[][])
    {
        return Arrays.stream(ts).flatMap(Stream::of);
    }

}

class Karton
{
    int getLaenge()
    {
        return 1;
    }

    int getAnzahlSchrauben()
    {
        return 5;
    }
}

aber vielleicht ist das nur ein Detail.

@Marco13 :
Auch dir, danke für deine Beteiligung.
Es handelt sich wahrhaftig um die räumliche Anordnung von gelagerten Kartons.

Vielleicht noch ein halb-off-topic-Nachtrag: Ich hatte mal eine kleine Lib erstellt für mehrdimensionale Arrays von Primitiven (!) Datentypen, und allgemein stellt sich bei solchen N-Dimensionalen Arrays auch die Frage nach der Reihenfolge, in der iteriert wird: https://github.com/javagl/ND/tree/master/nd-iteration#iteration-order Aber hier ist das vermutlich egal…