Java Methode optimieren / Distributivgesetz / OutOfMemory error

Master1991

Lieutenant
Registriert
Okt. 2007
Beiträge
686
Hi,

ich versuche für einen Alorithmus das "Distributivgesetz" zu implementieren (mit ein paar änderungen).

Beispiel:

(P4+P1+P9)(P9+P9)

ausmultipliziert wird es zu:

(P4P9+P4P9+P1P9+P1P9+P9P9+P9P9)

gewollt wäre hier nur P9 folgend den Regeln: X*X=X, XY+X=X, X+X=X (Es handelt sich um logik funktionen weshalb dies hier so gilt).

X*X=X habe ich in meinem Code bereits berücksichtigt sodass mein Ergebnis momentan

(P4P9+P4P9+P1P9+P1P9+P9+P9)

so aussieht.

Die anderen regeln wären noch zu implementieren (um die Problemgröße zu reduzieren, denn das wächst nicht linear).


Mein Hauptproblem ist momentan jedoch ein OutOfMemory error: heap space error.

Meine Idee ist nun den genutzten Speicher komplett vorher abzuschätzen und zu allokieren, daran scheitere ich momentan jedoch. es geht sozusagen um die verbesserung der multiplyArray() Methode. Der komplette Beispielcode:

PS: Ich benutze die Wrapper Klasse um direkt auf Methoden wie contains auf dem array zugreifen zu können. Ich habe es vorhin auch schon mit einen int[][][] array probiert, dort war meine implementerung jedoch langsamer.

Bei
public int rowValues = 10;
public int columnValues = 7;

bekommen ich dann den memory error wobei es zeittechnisches noch vollkommen okay wäre

Code:
package test;

import java.util.Arrays;
import java.util.Random;

public class DistributiveTest {

    private IntSetArray[][] data;
    public int maxValue = 9;
    public int rowValues = 2;
    public int columnValues = 3;
    public int maxEntrys = 1;
    private int index;

    public static void main(String[] args) {
        DistributiveTest dl = new DistributiveTest();
        dl.generateTestData();
        //Print Array
        System.out.println("Array (" + dl.data.length + " x " + dl.data[0].length + "): ");
        String sep = "";
        String prefix = "";
        System.out.print("(");
        for (int i = 0; i < dl.data.length; i++) {
            System.out.print(sep);
            sep = ")(";
            prefix = "";
            for (int j = 0; j < dl.data[i].length; j++) {
                if (dl.data[i][j] != null) {
                    System.out.print(prefix);
                    prefix = "+";
                    System.out.print(dl.data[i][j]);
                }

            }

        }
        System.out.println(")");

        //Multiply array
        dl.index = 0;
        long startTime = System.currentTimeMillis();
        while ((dl.index + 1) < dl.data.length) {
            dl.multiplyArray();
            dl.index += 1;
            //System.out.println(dl.productOfSums.toString());
        }
        long elapsedTime = System.currentTimeMillis() - startTime;
        System.out.println("Multiply took: " + elapsedTime + " ms");
        if (dl.data[dl.index].length < 20) {
            System.out.print("(");
            prefix = "";
            for (int i = 0; i < dl.data[dl.index].length; i++) {
                if (dl.data[dl.index][i] != null) {
                    System.out.print(prefix);
                    prefix = "+";
                    System.out.print(dl.data[dl.index][i]);
                }
            }
            System.out.print(")");
        }
    }

    private void generateTestData() {
        data = new IntSetArray[rowValues][columnValues];
        Random rand = new Random();
        for (int i = 0; i < rowValues; i++) {
            //Between 2 and columnValues
            int columnValue = rand.nextInt(columnValues - 1) + 2;
            for (int j = 0; j < columnValue; j++) {
                data[i][j] = new IntSetArray(maxValue + 1);
                int entrys = rand.nextInt(maxEntrys) + 1;
                for (int k = 0; k < entrys; k++) {
                    int value = rand.nextInt((int) maxValue + 1);
                    if (!data[i][j].contains(value)) {
                        data[i][j].add(value);
                    } else {
                        k--;
                    }

                }

            }
        }
    }

    private void multiplyArray() {
        IntSetArray[] is1 = data[index];
        IntSetArray[] is2 = data[index + 1];

        IntSetArray[] tmpArray = new IntSetArray[is1.length * is2.length];
        int loopCount = 0;
        for (int i = 0; i < is1.length; i++) {
            for (int j = 0; j < is2.length; j++) {
                if (is1[i] != null && is2[j] != null) {
                    IntSetArray tmp = new IntSetArray(maxValue + 1);
                    tmp.addAll(is1[i].getIntSet());
                    tmp.addAll(is2[j].getIntSet());
                    //is2[j].addAll(is1[i].getIntSet());
                    tmpArray[loopCount++] = tmp;
                }
            }
        }
        data[index] = null;
        data[index + 1] = tmpArray;
    }

    class IntSetArray {

        int[] intSet;
        int arraySize;
        int currentAddIndex;

        public IntSetArray(int size) {
            this.arraySize = size;
            this.intSet = new int[arraySize];
            Arrays.fill(intSet, -1);
            this.currentAddIndex = 0;
        }

        public void add(int i) {
            if (!contains(i) && i != -1) {
                intSet[currentAddIndex] = i;
                currentAddIndex++;
            }

        }

        public int[] getIntSet() {
            return intSet;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < intSet.length; i++) {
                if (!(intSet[i] == -1)) {
                    sb.append("P");
                    sb.append(intSet[i]);
                }

            }
            return sb.toString();
        }

        boolean contains(int value) {
            for (int i = 0; i < arraySize; i++) {
                if (intSet[i] == value) {
                    return true;
                } else if (intSet[i] == -1) {
                    return false;
                }
            }
            return false;
        }

        void addAll(int[] a) {
            for (int i = 0; i < a.length; i++) {
                if (a[i] == -1) {
                    break;
                }
                this.add(a[i]);
            }

        }

        int size() {
            return arraySize;
        }
    }
}
 
Ist denn der Speicherverbrauch wirklich das Problem oder braucht deine Anwendung schlicht einen größeren Heap, als du der Anwendung gibst?

Setzt du die maximale Heapgröße explizit? Falls nicht, hiermit kannst du schauen, welcher Wert für dein System gesetzt wird (MaxHeapSize):
Code:
java -XX:+PrintFlagsFinal -version
 
Yo!

Wie viel Speicher haben wir zur Verfügung?
Wie immer: Datenstruktur? Wie kommen die Daten rein, wie müssen sie raus?

Zusatz:
Gehst du davon aus, dass nur Summen multipliziert werden? Keine Subtraktion?

Zusatz 2:
Kann dein Ergebnis im Beitrag nicht nachvollziehen. Nach deinen zusätzlichen Regeln:

(P4+P1+P9)(P9+P9)
(P4+P1+P9)(P9)
(P4P9+P1P9+P9P9)
(P4P9+P1P9+P9)

Komme von hier definitiv nicht auf "nur P9".
 
Zuletzt bearbeitet:
KillerCow schrieb:
Ist denn der Speicherverbrauch wirklich das Problem oder braucht deine Anwendung schlicht einen größeren Heap, als du der Anwendung gibst?

Setzt du die maximale Heapgröße explizit? Falls nicht, hiermit kannst du schauen, welcher Wert für dein System gesetzt wird (MaxHeapSize):
Code:
java -XX:+PrintFlagsFinal -version

Naja es sollte schon mit den Standardwerten laufen. Die Anwendung wird ja auf verschiedenen Rechnern laufen

Killkrog schrieb:
Yo!

Wie viel Speicher haben wir zur Verfügung?
Wie immer: Datenstruktur? Wie kommen die Daten rein, wie müssen sie raus?

Zusatz:
Gehst du davon aus, dass nur Summen multipliziert werden? Keine Subtraktion?

Die Datenstruktur ist frei. In der ersten Implementierung waren es noch TreeSets und ArrayLists aber das dauert einfach zu lang. arrays werden wohl vom zugriff am schnellsten sein. Zudem representieren die integer oben eigendlich Objekte aber die Objekte durch die Methode zu tragen verursacht unnötig overhead. Ich such mir die Objekte anhand der Integer danach wieder zusammen.

Wie kommen die Daten rein -> Quasi so wie im Beispielcode eine Matrix mit einer Liste von Integern in den Zellen (Am Anfang wenig so max 2-3 Stück pro Zelle)

Wie müssen die Daten raus -> Ebenfalls wie im Beispiel, ich brauch halt das ausmultiplizierte Ergebnis und kann (sollte) dabei die vereinfachungen benutzen da das Problem es schon NP-hard ist.

Gehst du davon aus, dass nur Summen multipliziert werden? Keine Subtraktion -> Es sind keine Summen ... das ganze ist eine Logikfunktion * ist und + ist oder...die Methode macht aus einem Produkt von Summen eine Summe von Produkten --> Also ja keine "subtraktion"

Killkrog schrieb:
Kann dein Ergebnis im Beitrag nicht nachvollziehen. Nach deinen zusätzlichen Regeln:

(P4+P1+P9)(P9+P9)
(P4+P1+P9)(P9)
(P4P9+P1P9+P9P9)
(P4P9+P1P9+P9)

Komme von hier definitiv nicht auf "nur P9".

Benutz XY+X=X = P9+P9P1=P9

Sprachlich: P9 oder P9 und P1 ist gleich P9
 
Zuletzt bearbeitet:
Master1991 schrieb:
Benutz XY+X=X = P9+P9P1=P9
Übersehen :\

Schauen wir mal, was man da so schrauben kann...
Würde mich aber sehr wundern, wenn man da nicht deutlich runter kann mit dem Speicher. Dein Beispiel mit 10x7 braucht ja fast ein GByte an Speicher...
 
In multiplyArray() die Größe von tmpArray gibt nacheinander

49
343
2401
16807
117649
823543
5764801
40353607
282475249

mit
Code:
public int rowValues = 10;
public int columnValues = 7;

Ein Array mit 280 Millionen Elementen ist schon viel.
Auf die meisten Elemente davon wird auch nicht zugegriffen, da loopCount meist weit unter der Obergrenze von tmpArray bleibt. Höchste war mal 15 Million was ich gesehen habe.

Dadurch ergibt sich teilweise eine Heapgröße von bis zu 4GB.

Trotzdem wäre es zu überlegen ob multiplyArray() nicht vielleicht anders gestaltet werden sollte.
 
Im letzten Post ist bei mir erst der Groschen gefallen:
Du willst SAT lösen.
Ich präferiere eher einen anderen Ansatz:
Auf die allgemeine Form bringen, dann (wenn man es nicht anders explizit gefordert ist) den MinSAT solver verwenden.
Anderenfalls viel Spaß!
 
nien schrieb:
Auf die meisten Elemente davon wird auch nicht zugegriffen, da loopCount meist weit unter der Obergrenze von tmpArray bleibt. Höchste war mal 15 Million was ich gesehen habe.

Das ist ja das Problem vorher war es eine implementierung mit einer ArrayList aber die hat ständig das array neu kopiert. Daher die "abschätzung" vorher.


Ein bisschen mehr Input: Implementiert wird:
https://en.wikipedia.org/wiki/Petrick's_method

Dabei habe ich meine Programm auf 12 Variablen beschränkt (darüber wird der Algo sowieso vollkommen hinfällig).

Das macht im Worst Case 2^12 =4096 Spalten in der zu reduzierenden Tabelle. Dazu kommen dann die reihen. Hier ist jedoch das Problem das ich nicht wirklich abschätzen kann wie viele dies im Worst Case werden. Auch wie voll besetzt die Matrix ist, hängt von den Eingangsdaten ab.

Aber ja am ende gibts ein riesen array (das macht den algo ja so ineffizient):

Wenn die Nummern in den Klammern die Anzahl der Elemente darstellen
(2)(4)(5)(7)...sind es ohne Vereinfachung 2*4*5*7*... Elemente von max 2^12 Elementen.



EDIT: Nein es ist nicht SAT...aber ein ähnliches Problem. Eigendlich versucht die Methode ein minimales Cover zu finden das alle Spalten der Tabelle abdeckt (Siehe Wikipedia)
 
Zuletzt bearbeitet:
​Kleiner Vorschlag: das Array am Ende von multiplyArray() kürzen, so dass keine Elemente bleiben die nicht verwendet werden:

Code:
        tmpArray = Arrays.copyOf(tmpArray, loopCount);
        
        data[index] = null;
        data[index + 1] = tmpArray;
 
Zuletzt bearbeitet:
Annahme:
Eine Variable besteht aus ein oder mehr Werten aus der Menge {P1, ..., P16}.

Vorschlag:
Speichere eine Variable in einem einzigen Short.
Wegen X*X=X kann in einer Variable ein Wert immer nur einmal vorkommen => Bit-Operationen.
Je nach gesetztem Bit im Short ist P1-P16 gesetzt. Bei mehreren gesetzten Bit sind mehrere Werte in der Variable gesetzt.

Bei 10 Reihen und 7 Spalten haben wir 7^10 * 2 Byte = ca. 540 MByte

Wenn es noch weniger sein soll, musst du glaube ich SEHR stark auf Geschwindigkeit verzichten.
 
Das mit P1-P16 klingt ja verlockend...aber am ende koennen halt bis zu P4095 in einer "Variablen" sein ... das ist der hypotetische wors t case (ich weiß aber nicht ob es mathematisch moeglich ist, das er eintritt)aber selbst 1023 waeren ja viel ... letztendlich muesste ich den speichefr vorher allokieren um danach nurnoch darauf zuzugreifen (denn das erstellen von so einen großen int array im vorraus geht ohne speicher fehler) ... der garbage collegtor will wohl nichf recht mitkommen, mit den ganzen scheiß neuen arrays.

Ich koennte das result array vorher erstellen...aber ich weiß nicht wie ich es sinnvoll fuelle ohne die "rekursion" (Die ich aufgeloest hab da mit der stack um die ohren flog)
 
Ah, na dann werden es doch eher 7^10 * 7 * 2 Byte = 3,9 GB

Was anderes: Es löst zwar dein Problem nicht, aber mir ist noch etwas aufgefallen.

Deine IntSetArray Klasse ist unstatisch innerhalb der DistributiveTest Klasse.
Das bedeutet, dass jede Instanz von IntSetArray intern eine Referenz auf die DistributiveTest Instanz speichert. Das können bei den Mengen schnell einige MB werden.

Also entweder static machen oder in eine eigene Datei auslagern.
 
Zuletzt bearbeitet:
Hallo,

hast du vielleicht ein komplizierteres Beispiel das zeigt was am Ende rauskommen soll? Habe mich selbst ein wenig herumgespielt und einen ganz anderen Lösungsansatz verfolgt und bin mir nicht sicher ob ich nicht ein wenig zu naiv vorgegangen bin :D (schnell ist der Ansatz und Speicher verbraucht er auch kaum - beim Input

((P7+P1+P4+P9+P5+P3)*(P12+P6+P11+P10)*(P12)*(P2+P3+P1+P2+P2+P12+P3)*(P11+P12+P6+P11+P9+P1)*(P1+P12+P2+P6)*(P7+P4+P8)*(P5+P8)*(P4+P2+P9+P8+P6)*(P9+P12+P2+P7+P12+P7))

kommt ich extrem schnell zu einem extrem langen Ergebnis (ohne viel Speicher zu verbauchen). Nur ob die Lösung richtig ist weiss ich nicht.

PS: Dein Beispiel z.B. wird richtig auf P9 reduziert
 
Henni schrieb:
...

((P7+P1+P4+P9+P5+P3)*(P12+P6+P11+P10)*(P12)*(P2+P3+P1+P2+P2+P12+P3)*(P11+P12+P6+P11+P9+P1)*(P1+P12+P2+P6)*(P7+P4+P8)*(P5+P8)*(P4+P2+P9+P8+P6)*(P9+P12+P2+P7+P12+P7))

kommt ich extrem schnell zu einem extrem langen Ergebnis (ohne viel Speicher zu verbauchen). Nur ob die Lösung richtig ist weiss ich nicht.

Es sollte auch noch relativ schnell gehen bis zu einer Anzahl X ... 10_6 dauert bei mir auch nur 1.2sek auf gammel laptop. Das Problem mit komplizierten Beispielen ist halt die laenge...ich bau dir gleich was komplizierteres...das einzige was in deinem beispielt fehlr waere die moeglichtkeit das (P2P3+P4) vorkommen kann...also zwei variablen per mal verknuepft...sollte das jedoch ein Problem darstellen kann darauf verzichtet werden. Vll magst du mich einfach mal an deinem code teilbaben lassen
Ergänzung ()

So ein Beispiel "aus dem echten" Programm:

nehmen wir 5 Variablen haben wir maximal 2^5= 32 "Variablen" P0-P31.

Ein durchlauf mit zufälligen Werten bringt zB

(P8+P9)(P1+P11)(P11+P12)(P1+P8)(P9+P10)(P10+P13)(P12+P13) zum minimieren, das ist bereits zusammenfassbar

mit (X+Y)(X+Z) = X+YZ das passiert auch schon sodass folgendes vorliegt:

(P1+P8P11)(P9+P8P10)(P12+P11P13)(P10+P13).

Das ist schonmal kleiner als obriges. Nun wird multipliziert:

(P1P9P10P12+P1P9P12P13+P1P9P10P11P13+P1P9P11P13+P1P8P10P12+P1P8P10P12P13+P1P8P10P11P13+P1P8P10P11P13+P8P9P10P11P12+P8P9P11P12P13+P8P9P10P11P13+P8P9P11P13+P8P10P11P12+P8P10P11P12P13+P8P10P11P13+P8P10P11P13)

Leider ist das nun sortiert, sodass es vll etwas schwer nachzuvollziehen ist. Was hier nach wie vor fehlt wäre die Anwendung der Regeln X+X=X; X+XY=X.

Die wären aber nur wichtig wenn sie zeiteffizient angewendet werden können.

Am ende ist das Ziel den Term zu wählen der die geringsten Variablen beinhaltet.

Als Beispiel nochmal eine Regel angewendet: P8P10P11P12P13 fällt zB weg wegen P8P10P11P13 un XY+X=X.


Meine Grundsätzliche Idee wäre nun das result array vorher zu erzeugen ... was ja offensichtlich ein IntSetArray[] ist. Eindimensional. Die maximale Größe (ohne Regeln) wäre auch vorher bekannt: Die länge der einzelnen Abschnitte multipliziert.
(P8+P9)(P1+P11)(P11+P12)(P1+P8)(P9+P10)(P10+P13)(P12+P13)
2 * 2 * 2 * 2...=2^7= 128.

IntSetArray[128] ... wobei in jedem Eintrag ein IntSetArray wäre mit der länge der maximal vorkommenden Variablen (denn es kann keine doppelten geben). In diesem Fall P8, P9, P10, P11, P12, P13 . Also 128 IntSetArrays der länge 6.

Jetzt mangelt es mir nur an der Fähigkeit das Ergebnisarry ohne rekursion zu füllen (sozusagen in einem rutsch)...das würde vermeiden ständig neue Arrays zu erstellen und würde den Garbage Collector entlasten.

Nur theoretisch brauch ich ja nun eine Schleife für jeden Klammerblock.

Ergebnis an der Stelle 0 wäre ja einfach das erste Element jeder Klammer:
(P8+P9)(P1+P11)(P11+P12)(P1+P8)(P9+P10)(P10+P13)(P12+P13)

also P8P1P11P9P10P12 ... zweiter eintrag die hinterste Klammer um eins erhöht P8P1P11P9P10P13...nun würde die letzte Klammer wieder auf 0 springen und die Vorletzte Klammer erhöht: P8P1P11P9P13P12.

Ist dies in Code zu gießen? Vielleicht komme ich auch einfach nicht drauf und es wäre einfach:D
 
Also, wenn es dir nur darum geht, eine dynamische Anzahl von verschachtelten for-Schleifen zu benutzen, hilft dir sowas?

DynamicLoops.java
Code:
import java.util.Arrays;

public class DynamicLoops {

    // ===================================================================
    // {[> Initializers and Constructors
    // =================================
    public DynamicLoops() {

        int[] curIndices = new int[]{0, 0, 0};
        int[] maxIndices = new int[]{2, 3, 2};

        while (curIndices[0] != -1) {
            System.out.println(String.format("%d,%d,%d", curIndices[0], curIndices[1], curIndices[2]));
            next(curIndices, maxIndices);
        }
    }



    // ===================================================================
    // {[> Private Static Methods
    // ==========================
    private static void next(int[] curIndices, int[] maxIndices) {

        for (int i = 0; i < curIndices.length; i++) {

            if (curIndices[i] < maxIndices[i]) {
                curIndices[i]++;
                return;
            } else {
                curIndices[i] = 0;
            }
        }

        Arrays.fill(curIndices, -1);
    }



    // ===================================================================
    // {[> Public Static Methods
    // =========================
    public static void main(String[] args) {
        new DynamicLoops();
    }
}
Ausgabe
Code:
0,0,0
1,0,0
2,0,0
0,1,0
1,1,0
2,1,0
0,2,0
1,2,0
2,2,0
0,3,0
1,3,0
2,3,0
0,0,1
1,0,1
2,0,1
0,1,1
1,1,1
2,1,1
0,2,1
1,2,1
2,2,1
0,3,1
1,3,1
2,3,1
0,0,2
1,0,2
2,0,2
0,1,2
1,1,2
2,1,2
0,2,2
1,2,2
2,2,2
0,3,2
1,3,2
2,3,2

Damit kannst du bequem alle Klammern durchrutschen und miteinander multiplizieren. Anhand der Indizes berechnest du dann den Index in deinem endgültigen Array und packst das Ergebnis da rein. So müsstest du nur einmal ein Array erzeugen. Der eigentliche Speicherverbrauch bleibt allerdings unberührt groß.
 
Killkrog schrieb:
Also, wenn es dir nur darum geht, eine dynamische Anzahl von verschachtelten for-Schleifen zu benutzen, hilft dir sowas?

Großartig, ich denke das hilft. Ich probier mal bisschen.

Die Frage die sich dann stellt ist halt: Speicher/Laufzeit.

Theoretisch muss X+X=X bzw XY+X=X ja nicht angewendet werden. Am ende such ich den kürzesten Eintrag. ABER: Das Array lässt sich natürlich drastisch reduzieren zu lasten der Laufzeit.

Nehmen wir P0-P3 dann gibt es als kombinationsmöglihkeit ja nur:

Entweder ist es nur:
P0P1P2P3

oder:
P0P1P2
P0P1P3
P0P2P3
P1P2P3

oder:
P0P1
P0P2
P0P3
P1P2
P1P3
P2P3

oder:
P0
P1
P2
P3

Und kombinationen wie P0,P1P2,P1P3,P2P3 ... aber wenn ich micht nicht vertue (müsste ich nochmal drüber nachdenken) wäre die maximale Anzahl von Einträgen damit: n(n+1)/2 (nämlich alle zweierkombinationen) wobei n dann die Anzahl der vorkommenden variablen ist.
Ich probier jetzt erstmal
Ergänzung ()

So, habe das mal umgebaut und die Struktur geändert, sodass es nun auch deutlich schneller sein sollte.

Leider belegt ein boolean scheinbar 1 Byte?! in Java wodurch ich die größe nicht reduziert kriege.


Ich versuch den Code so mal ins richtige Programm zu übernehmen und schau dann wie weit ich damit komme oder ob ich die optiierungen doch noch einbauen muss um den Speicher runter zu kriegen:

Anmerkung: Komisch finde ich das ein reines boolean[][][] Array ohne Wrapperklasse nicht initializierbar ist mit data = new boolean[10][6][10] wegen heap space, mit Wrapper das Programm jedoch läuft und schnell zum Ziel kommt.

So nebenbei brauche ich jetzt schlaue Ideen X+X=X schnell zu erkennen, sowie XY+X=X. Ideen?

Code:
package test;

import java.util.Arrays;
import java.util.Random;

public class DistributiveTest {

    private BooleanSet[][] data;
    BooleanSet[] result;
    int[] curIndices;
    int[] maxIndices;
    public int maxValue = 9;
    public int rowValues = 10;
    public int columnValues = 9;
    public int maxEntrys = 2;

    public static void main(String[] args) {
        DistributiveTest dl = new DistributiveTest();
        dl.generateTestData();
        dl.printInitial();

        dl.curIndices = new int[dl.data.length];
        dl.maxIndices = new int[dl.data.length];
        //Generate maxIndizes and result size
        int resultSize = 1;
        for (int i = 0; i < dl.data.length; i++) {
            int max = 0;
            for (BooleanSet item : dl.data[i]) {
                if (item != null) {
                    max++;
                }
            }
            dl.maxIndices[i] = max - 1;
            resultSize *= max;
        }

        dl.result = new BooleanSet[resultSize];
        long startTime = System.currentTimeMillis();
        dl.multiply();
        long elapsedTime = System.currentTimeMillis() - startTime;
        System.out.println("Multiply took: " + elapsedTime + " ms");
        dl.printResult();
    }

    private void generateTestData() {
        data = new BooleanSet[rowValues][columnValues];
        Random rand = new Random();
        for (int i = 0; i < rowValues; i++) {
            //Between 2 and columnValues
            int columnValue = rand.nextInt(columnValues - 1) + 2;
            for (int j = 0; j < columnValue; j++) {
                data[i][j] = new BooleanSet(maxValue + 1);
                int entrys = rand.nextInt(maxEntrys) + 1;
                for (int k = 0; k < entrys; k++) {
                    int value = rand.nextInt((int) maxValue + 1);
                    if (!data[i][j].contains(value)) {
                        data[i][j].add(value);
                    } else {
                        k--;
                    }

                }

            }
        }
    }

    private static void next(int[] curIndices, int[] maxIndices) {

        for (int i = 0; i < curIndices.length; i++) {
            if (curIndices[i] < maxIndices[i]) {
                curIndices[i]++;
                return;
            } else {
                curIndices[i] = 0;
            }
        }

        Arrays.fill(curIndices, -1);
    }

    public void printInitial() {
        System.out.println("Array (" + data.length + " x " + data[0].length + "): ");
        String sep = "";
        String prefix = "";
        System.out.print("(");
        for (BooleanSet[] dataOuter : data) {
            System.out.print(sep);
            sep = ")(";
            prefix = "";
            for (BooleanSet dataInner : dataOuter) {
                if (dataInner != null) {
                    System.out.print(prefix);
                    prefix = "+";
                    System.out.print(dataInner);
                }
            }
        }
        System.out.println(")");

    }

    public void printResult() {
        if (result.length < 2000) {
            System.out.print("(");
            String prefix = "";
            for (BooleanSet bs : result) {
                System.out.print(prefix);
                prefix = "+";
                System.out.print(bs.toString());
            }
            System.out.print(")");
       }

    }

    private void multiply() {
        int addIndex = 0;
        for (BooleanSet r : result) {
            BooleanSet tmp = new BooleanSet(maxValue + 1);
            for (int i = 0; i < data.length; i++) {
                tmp.addAll(data[i][curIndices[i]].getBooleanSet());

            }
            next(curIndices, maxIndices);
            result[addIndex++] = tmp;
        }
    }

    //Maybe a short set is smaller
    static class BooleanSet {

        boolean[] booleanSet;

        public BooleanSet(int size) {
            //Is initialized to false
            this.booleanSet = new boolean[size];
        }

        //Just flag the index as used
        public void add(int i) {
            booleanSet[i] = true;
        }

        public boolean[] getBooleanSet() {
            return booleanSet;
        }

        void addAll(boolean[] a) {
            for (int i = 0; i < a.length; i++) {
                if (a[i]) {
                    this.add(i);
                }
            }
        }

        boolean contains(int value) {
              return booleanSet[value];

        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < booleanSet.length; i++) {
                if (booleanSet[i]) {
                    sb.append("P");
                    sb.append(i);
                }

            }
            return sb.toString();
        }
    }
}
 
Zuletzt bearbeitet:
Also, ich muss jetzt ja doch noch einmal genauer Nachharken...
Wieso möchtest du überhaupt unbedingt den Speicherverbrauch verkleinern?
Bist du an ein bestimmtes Zielsystem gebunden, oder einfach nur "weil halt"?

Denn selbst wenn du den Speicher im Vorfeld allokierst um den GC zu schonen und auch nur in der Größe, wie dein aktueller Fall benötigt, so hast du immer noch nen Worst-Case, der einfach irgendwann einmal auftreten wird. Und dann wird dir wieder alles um die Ohren fliegen.

Ansonsten, ja, Boolean ist immer ein Byte groß. Wenn du sehr viele Boolean Werte speichern möchtest, dann kannst du dir das BitSet ansehen.

Außerdem könntest du immer noch meine erste Idee auffassen, FALLS nie mehr als 32/64 verschiedene Buchstaben verwendet werden. Dann speicherst du die in einem Integer/Long und zusätzlich irgendwo ein Übersetzungsarray, dass die tatsächlichen Buchstaben auf 0-31/63 übersetzt.

P.S.: Ist es tatsächlich so, dass du als Eingangsmaterial Fälle mit maxEntries > 1 bekommst?
 
Zuletzt bearbeitet:
Killkrog schrieb:
Also, ich muss jetzt ja doch noch einmal genauer Nachharken...
Wieso möchtest du überhaupt unbedingt den Speicherverbrauch verkleinern?
Bist du an ein bestimmtes Zielsystem gebunden, oder einfach nur "weil halt"?

Denn selbst wenn du den Speicher im Vorfeld allokierst um den GC zu schonen und auch nur in der Größe, wie dein aktueller Fall benötigt, so hast du immer noch nen Worst-Case, der einfach irgendwann einmal auftreten wird. Und dann wird dir wieder alles um die Ohren fliegen.

Ansonsten, ja, Boolean ist immer ein Byte groß. Wenn du sehr viele Boolean Werte speichern möchtest, dann kannst du dir das BitSet ansehen.

Außerdem könntest du immer noch meine erste Idee auffassen, FALLS nie mehr als 32/64 verschiedene Buchstaben verwendet werden. Dann speicherst du die in einem Integer/Long und zusätzlich irgendwo ein Übersetzungsarray, dass die tatsächlichen Buchstaben auf 0-31/63 übersetzt.

P.S.: Ist es tatsächlich so, dass du als Eingangsmaterial Fälle mit maxEntries > 1 bekommst?

Naja den Speicherverbrauch verkleinern musste ich da ich den heap Error bekommen habe bei 12 Variablen und somit das Programm nicht fertig wurde. Ich habe nun die aktuelle Version noch nicht ausprobiert, aber es mag gut sein da es nun bereits reicht.

Von BitSet habe ich auch schon gelesen vorhin, schaue ich mir an sollte es so nun nicht reichen.

Ich habe deine Idee tatsächlich jetzt gerade erst verstanden. Es können nach wie vor mehr als 64 Einträge sein aber vermutlich deutlich weniger als 1000 im worst case (ich bin zu doof um das auszurechnen).

Aber dann kann ich ja trotzdem ein array[long] erstellen und das bitweise durchadressieren und mit bitmasken die jeweiligen einträge setzen. Das spart ja riesig statt boolean[10] = 80 byte ein long mit 8 byte. Habe dann halt overhead durch indexberechung (welches long ist das richtige), aber naja das ist sicher im rahmen.

maxEntries > 1 könnte man ignorieren. Die Rohdaten sind immer (a+b)(b+d)(e+i)(i+e) sowas. Das ist aber vereinfachbar (a+b)(b+d)=(b+ad) ... dadurch kommen maxEntries > 1 zustande. Aber eben weniger Klammern zum multiplizieren.

Da ich am ende auch das minimum suche und mitlerweile die ausmultiplizierung "in einem rutsch" mache kann ich das aktuell kürzeste ergebnis auch zwischenspeichern und sobald ich etwas kleineres finde das komplette ergebnis array löschen und neu erstellen. Das löst dann auch gleich XY+X=X (und mehr). Verkeinert das Ergebnis array extrem. Die Frage ist halt:

arrays verkleinern geht nicht -> Also das ergebnis array als ArrayList anlegen und dynamisch erweitern, oder eigenständig kopieren/löschen
 
Master1991 schrieb:
Aber dann kann ich ja trotzdem ein array[long] erstellen und das bitweise durchadressieren und mit bitmasken die jeweiligen einträge setzen.
Jetzt weißt du, was an BitSet macht ;)

Master1991 schrieb:
Ich habe deine Idee tatsächlich jetzt gerade erst verstanden. Es können nach wie vor mehr als 64 Einträge sein aber vermutlich deutlich weniger als 1000 im worst case (ich bin zu doof um das auszurechnen).
Naja, bei 10 Klammern mit jeweils 7 Startvariablen können es im Worst-Case 10*7 unterschiedliche Variablen sein, oder hab ich was falsch verstanden? Das könnte man dann sogar noch mit zwei Longs realisieren.
 
Zuletzt bearbeitet:
Killkrog schrieb:
Naja, bei 10 Klammern mit jeweils 7 Startvariablen können es im Worst-Case 10*7 unterschiedliche Variablen sein, oder hab ich was falsch verstanden? Das könnte man dann sogar noch mit zwei Longs realisieren.

Ah, dann haben wir doch aneinander vorbeigeredet. Ich dachte du redest von der bitSet idee. Dann habe ich deinen Vorschlag doch noch nicht verstanden. Werde nun aber auch erstmal das jetzige implementieren ich denke das langt schon.

Ja im endergebnis können dann 10^7 sein. Ich redete von den Einträgen im bitSet. Dort kann ich den worst case nicht recht ausrechnen. Mehr als 2^Eingangsvaraiblen können es nicht sein, aber vermutlich ist das nicht das Kap. Aber davon gehe ich im WorstCase aus. Also ein BitSet mit max 2^12 Einträgen.
Ergänzung ()

Um das ganze zum Abschluss zu bringen: Danke Killkrog, du hast wieder enorm geholfen durch die dynamischen Schleifen.

Das war dann halt letztendlich der Key, denn wenn das Ergebnis einer kompletten Multiplikation bereits nach einem Methodenaufruf vorliegt kennt man die Größe des Ergebnisses. Da nur das/die kleinsten Ergebnisse gesucht werden (Minimales Cover der Tabelle) kann man bereits während der erstellung des Arrays abbrechen sobald das aktuelle Element mehr variablen beinhaltet als das aktuell kleinste. Es kann im nachhinein nicht mehr kleiner werden. Dadurch haben wir ein Abbruchkriterium und absolut kein Problem mehr mit der Methode, weder was Geschwindigkeit, noch was speicher angeht.

Ich hoffe ich habe keinen bösen denkfehler gemacht und die n/(n+1)/2 Elemente im worst case stimmen (bisher hat es geklappt) ... Es ist nunmehr ohne Probleme möglich ein 2000x2000 Array mit beliebigen Einträgen quasi ohne Zeitverzug zu lösen.

Der Code nun: (Falls es dazu anmerkungen gibt bitte, vll ist ein böser fehler drin)

@Killkrog: BitSet hatte ich zwischenzeitlich ausprobiert, ist aber wesentlich langsamer als das booleanSet. Da es keine Speicherprobleme mehr gibt ist es nun aber auch alles egal.


Code:
package test;

import java.util.Arrays;
import java.util.Random;

public class DistributiveTestMultiplyAbort {

    private BooleanSet[][] data;
    BooleanSet[] result;
    int[] curIndices;
    int[] maxIndices;
    public int maxValue = 90;
    public int rowValues = 2000;
    public int columnValues = 2000;
    public int maxEntrys = 9;
    public int smallest = Integer.MAX_VALUE;

    public static void main(String[] args) {
        DistributiveTestMultiplyAbort dl = new DistributiveTestMultiplyAbort();
        dl.generateTestData();
        //dl.printInitial();

        dl.curIndices = new int[dl.data.length];
        dl.maxIndices = new int[dl.data.length];
        //Generate maxIndizes and result size
        int resultSize = 1;
        for (int i = 0; i < dl.data.length; i++) {
            int max = 0;
            for (BooleanSet item : dl.data[i]) {
                if (item != null) {
                    max++;
                }
            }
            dl.maxIndices[i] = max - 1;
            resultSize *= max;
        }
        if (resultSize <= 0) {
            resultSize = Integer.MAX_VALUE;
        }
        resultSize = Math.min(resultSize, dl.maxValue * (dl.maxValue + 1) / 2);
        dl.result = new BooleanSet[resultSize];
        long startTime = System.currentTimeMillis();
        dl.multiply();
        long elapsedTime = System.currentTimeMillis() - startTime;
        System.out.println("Multiply took: " + elapsedTime + " ms");
        dl.printResult();
    }

    private void generateTestData() {
        data = new BooleanSet[rowValues][columnValues];
        Random rand = new Random();
        for (int i = 0; i < rowValues; i++) {
            //Between 2 and columnValues
            int columnValue = rand.nextInt(columnValues - 1) + 2;
            for (int j = 0; j < columnValue; j++) {
                data[i][j] = new BooleanSet(maxValue + 1);
                int entrys = rand.nextInt(maxEntrys) + 1;
                for (int k = 0; k < entrys; k++) {
                    int value = rand.nextInt((int) maxValue + 1);
                    if (!data[i][j].contains(value)) {
                        data[i][j].set(value);
                    } else {
                        k--;
                    }

                }

            }
        }
    }

    private static void next(int[] curIndices, int[] maxIndices) {

        for (int i = 0; i < curIndices.length; i++) {
            if (curIndices[i] < maxIndices[i]) {
                curIndices[i]++;
                return;
            } else {
                curIndices[i] = 0;
            }
        }

        Arrays.fill(curIndices, -1);
    }

    public void printInitial() {
        System.out.println("Array (" + data.length + " x " + data[0].length + "): ");
        String sep = "";
        String prefix = "";
        System.out.print("(");
        for (BooleanSet[] dataOuter : data) {
            System.out.print(sep);
            sep = ")(";
            prefix = "";
            for (BooleanSet dataInner : dataOuter) {
                if (dataInner != null) {
                    System.out.print(prefix);
                    prefix = "+";
                    System.out.print(dataInner);
                }
            }
        }
        System.out.println(")");

    }

    public void printResult() {
        System.out.print("(");
        String prefix = "";
        for (BooleanSet bs : result) {
            if (bs != null) {
                System.out.print(prefix);
                prefix = "+";
                System.out.print(bs.toString());
            }
        }
        System.out.print(")");
    }

    private void multiply() {
        int addIndex = 0;
        for (BooleanSet r : result) {
            BooleanSet tmp = new BooleanSet(maxValue + 1);
            for (int i = 0; i < data.length; i++) {
                tmp.setAll(data[i][curIndices[i]].getBooleanSet());
                if (tmp.bitsSet() > smallest) {
                    break;
                }

            }
            next(curIndices, maxIndices);

            int setBits = tmp.bitsSet();
            if (setBits < smallest) {
                smallest = setBits;
                Arrays.fill(result, null);
                addIndex = 0;
            }
            if (setBits == smallest) {
                result[addIndex++] = tmp;
            }
        }
    }

    //Maybe a short set is smaller
    static class BooleanSet {

        boolean[] booleanSet;
        short setBits;

        public BooleanSet(int size) {
            //Is initialized to false
            this.booleanSet = new boolean[size];
            this.setBits = 0;
        }

        //Just flag the index as used
        public void set(int i) {
            if (!booleanSet[i]) {
                booleanSet[i] = true;
                setBits++;
            }
        }

        void setAll(boolean[] a) {
            for (int i = 0; i < a.length; i++) {
                if (a[i]) {
                    this.set(i);
                }
            }
        }

        public boolean[] getBooleanSet() {
            return booleanSet;
        }

        public int bitsSet() {
            return setBits;
        }

        boolean contains(int value) {
            return booleanSet[value];
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < booleanSet.length; i++) {
                if (booleanSet[i]) {
                    sb.append("P");
                    sb.append(i);
                }

            }
            return sb.toString();
        }
    }
}
Ergänzung ()

Mit der geänderten result size muss es im multiply natürlich nun auch while(curIndices[0] != -1) heißen. Damit ist alles beim alten und die Methode wie erwartet langsam.

Das speicherproblem ist jedoch nun gelöst und damit alles gut. Besser geht es eben nicht. NP schweres problem.
 
Zurück
Oben