Hallo Leute,
ich programmiere schon sehr lange und sehr viel Java, aber gerade versuche ich ein Problem zu lösen, welches mich in den Wahnsinn treibt. Daher meine Hoffnung, dass jemand von euch vielleicht eine gute Idee hat.
Es geht um folgendes. Es gibt ein zwei-dimensionales NxN Array von double-Werten, das sieht z.B. so für N = 3 aus:
0.2 | 0.4 | 0.3
0.1 | 1.0 | 0.2
0.4 | 0.4 | 0.9
Ziel des Algorithmus ist es, aus jeder Reihe 1 Element auszuwählen. Die Zahlen geben dabei die Wahrscheinlichkeiten relativ zueinander an. Also eine Spalte mit 1.0 soll entsprechend wahrscheinlicher sein, als eine Spalte mit z.B. 0.4. Aber 1.0 heißt nicht, dass diese Spalte zu 100% gewählt wird. Es ist nur sehr viel wahrscheinlicher als eine Spalte mit 0.4.
Das ist eigentlich kein so schweres Problem. Ich habe erstmal so angefangen, dass ich die Summe jeder Reihe berechnet habe - und zwar bevor der eigentliche Algorithmus startet. Das wird also schon zu dem Zeitpunkt gemacht, bei dem das Array mit Werten gefüllt wird. Dann sah der Algo in etwa so aus:
Ansatz 1:
Nehmen wir z.B. die erste Zeile aus obigem Beispiel:
0.2 | 0.4 | 0.3 (Summe = 0.9)
-> Spalte 1 für Zufallszahlen [0 - 0.2[
-> Spalte 2 für Zufallszahlen [0.2 - 0.6[
-> Spalte 3 für Zufallszahlen [0.6 - 0.9[
So weit so gut. Jetzt ist dieser Algorithmus aber leider nicht besonders performant, im Worst Case O(n²). Mein Programm muss diesen Code aber mehr als 300 Millionen mal aufrufen. Und diese 300 Millionen mal sollten möglichst schnell gehen, da das Programm oft ausgeführt werden soll, um Auswirkungen verschiedener Parameter auf das Programmergebnis zu testen.
Mit anderen Worten -> dieses Problem muss irgendwie mit einem schnelleren Algorithmus gelöst werden.
N ist für die Daten mit denen ich arbeite praktisch immer 100. Eine 100 X 100 Matrix 300 Millionen mal ganz durchzulaufen ist schon viel. Also hatte ich folgende Idee:
Bevor der Algorithmus ausgeführt wird - zum Zeitpunkt der anfänglichen Belegung der Matrix mit den Werten - teile ich die Spalten in Gruppen auf. Bei N = 100 nehme ich 12 Gruppen zu jeweils 8 Spalten. 12 * 8 ist aber nur 96, deswegen ist die erste Gruppe 12 statt 8 Spalten groß. Zu jeder Gruppe speichere ich die Zwischensumme. So kann während des Algorithmus schnell bestimmt werden, zu welcher Gruppe die Zufallszahl gehört (Worst Case 12 Elemente durchgehen). Dann muss nur noch die Suche aus dem bisherigen Algorithmus pro Reihe auf maximal 8 Elemente (bzw. maximal 12 für die erste Gruppe) durchgeführt werden, anstatt auf 100. Das sollte schon einen großen Performance-Gewinn bringen. Der Algorithmus sah dann in etwa so aus:
Ansatz 2:
Die zwei Algorithmen, die ich jetzt gepostet habe, funktionieren auch beide. Aber da ich es jetzt nicht 1-1 den alten Stand kopieren konnte, können sich kleine Fehler beim Tippen ins Forum ergeben haben. Darum geht es aber nicht wirklich.
Denn mein Problem kommt erst jetzt. Es gibt nämlich noch eine weitere Anforderung:
- Wurde eine Spalte in einer Reihe bereits ausgewählt, so darf sie in späteren Reihen nicht nochmals gewählt werden.
Dafür finde ich einfach keine Lösung - oder jedenfalls keine, die nicht die Performance-Verbesserung direkt wieder zunichte machen würde. Ich habe noch ein boolean[] ergänzt, das die Spalten repräsentiert und dort true ist, wo eine Spalte bereits ausgewählt wurde. Dann überprüfe ich das. Das große Problem dabei markiere ich im folgenden Code als Kommentar:
Ansatz 3:
Wäre echt dankbar, falls jemand eine Idee hat?
Gruß
ich programmiere schon sehr lange und sehr viel Java, aber gerade versuche ich ein Problem zu lösen, welches mich in den Wahnsinn treibt. Daher meine Hoffnung, dass jemand von euch vielleicht eine gute Idee hat.
Es geht um folgendes. Es gibt ein zwei-dimensionales NxN Array von double-Werten, das sieht z.B. so für N = 3 aus:
0.2 | 0.4 | 0.3
0.1 | 1.0 | 0.2
0.4 | 0.4 | 0.9
Ziel des Algorithmus ist es, aus jeder Reihe 1 Element auszuwählen. Die Zahlen geben dabei die Wahrscheinlichkeiten relativ zueinander an. Also eine Spalte mit 1.0 soll entsprechend wahrscheinlicher sein, als eine Spalte mit z.B. 0.4. Aber 1.0 heißt nicht, dass diese Spalte zu 100% gewählt wird. Es ist nur sehr viel wahrscheinlicher als eine Spalte mit 0.4.
Das ist eigentlich kein so schweres Problem. Ich habe erstmal so angefangen, dass ich die Summe jeder Reihe berechnet habe - und zwar bevor der eigentliche Algorithmus startet. Das wird also schon zu dem Zeitpunkt gemacht, bei dem das Array mit Werten gefüllt wird. Dann sah der Algo in etwa so aus:
Ansatz 1:
Java:
for (int i = 0; i < N; i++) {
// Zufallszahl zwischen [0, Zeilensumme[
double random = RANDOM.nextDouble() * rowSummations[i];
// Über alle Felder gehen, dabei Summe mit hochzählen
double sum = 0;
for (int j = 0; j < N; j++) {
sum += matrix[i][j];
// Wenn gezählte Summe > Zufallszahl, dann Spalte gefunden
if (sum > random) {
result.setColForRow(i, j);
break;
}
}
}
Nehmen wir z.B. die erste Zeile aus obigem Beispiel:
0.2 | 0.4 | 0.3 (Summe = 0.9)
-> Spalte 1 für Zufallszahlen [0 - 0.2[
-> Spalte 2 für Zufallszahlen [0.2 - 0.6[
-> Spalte 3 für Zufallszahlen [0.6 - 0.9[
So weit so gut. Jetzt ist dieser Algorithmus aber leider nicht besonders performant, im Worst Case O(n²). Mein Programm muss diesen Code aber mehr als 300 Millionen mal aufrufen. Und diese 300 Millionen mal sollten möglichst schnell gehen, da das Programm oft ausgeführt werden soll, um Auswirkungen verschiedener Parameter auf das Programmergebnis zu testen.
Mit anderen Worten -> dieses Problem muss irgendwie mit einem schnelleren Algorithmus gelöst werden.
N ist für die Daten mit denen ich arbeite praktisch immer 100. Eine 100 X 100 Matrix 300 Millionen mal ganz durchzulaufen ist schon viel. Also hatte ich folgende Idee:
Bevor der Algorithmus ausgeführt wird - zum Zeitpunkt der anfänglichen Belegung der Matrix mit den Werten - teile ich die Spalten in Gruppen auf. Bei N = 100 nehme ich 12 Gruppen zu jeweils 8 Spalten. 12 * 8 ist aber nur 96, deswegen ist die erste Gruppe 12 statt 8 Spalten groß. Zu jeder Gruppe speichere ich die Zwischensumme. So kann während des Algorithmus schnell bestimmt werden, zu welcher Gruppe die Zufallszahl gehört (Worst Case 12 Elemente durchgehen). Dann muss nur noch die Suche aus dem bisherigen Algorithmus pro Reihe auf maximal 8 Elemente (bzw. maximal 12 für die erste Gruppe) durchgeführt werden, anstatt auf 100. Das sollte schon einen großen Performance-Gewinn bringen. Der Algorithmus sah dann in etwa so aus:
Ansatz 2:
Java:
for (int i = 0; i < N; i++) {
// Zufallszahl von [0 - Zeilensumme[ (die Gruppen-Summen sind in der zweiten Dimension von rowSummations)
double randomNumber = RANDOM.nextDouble() * rowSummations[i][numberGroups - 1];
// Finde richtige Gruppe
int group = 0;
for (int j = 0; j < rowSummations[i].length; j++) {
if (randomNumber < rowSummations[i][j]) {
group = j;
break;
}
}
// Erster und letzter Eintrag von Gruppe bestimmen
// Erste Gruppe ist potentiell größer als die anderen
int first = group == 0 ? 0 : enlargementFirstGroup + colsPerGroup * group;
int last = group == 0 ? colsPerGroup - 1 + enlargementFirstGroup : first + colsPerGroup - 1;
// Suche Eintrag in Gruppe
double sum = group == 0 ? 0 : rowSummations[i][group - 1];
for (int j = first; j <= last; j++) {
sum += matrix[i][j];
if (randomNumber < sum) {
result.setColForRow(i, j);
break;
}
}
}
Die zwei Algorithmen, die ich jetzt gepostet habe, funktionieren auch beide. Aber da ich es jetzt nicht 1-1 den alten Stand kopieren konnte, können sich kleine Fehler beim Tippen ins Forum ergeben haben. Darum geht es aber nicht wirklich.
Denn mein Problem kommt erst jetzt. Es gibt nämlich noch eine weitere Anforderung:
- Wurde eine Spalte in einer Reihe bereits ausgewählt, so darf sie in späteren Reihen nicht nochmals gewählt werden.
Dafür finde ich einfach keine Lösung - oder jedenfalls keine, die nicht die Performance-Verbesserung direkt wieder zunichte machen würde. Ich habe noch ein boolean[] ergänzt, das die Spalten repräsentiert und dort true ist, wo eine Spalte bereits ausgewählt wurde. Dann überprüfe ich das. Das große Problem dabei markiere ich im folgenden Code als Kommentar:
Ansatz 3:
Java:
boolean[] addedCols = new boolean[N]; // NEU
for (int i = 0; i < N; i++) {
// Zufallszahl von [0 - Zeilensumme[ (die Gruppen-Summen sind in der zweiten Dimension von rowSummations)
double randomNumber = RANDOM.nextDouble() * rowSummations[i][numberGroups - 1];
// Finde richtige Gruppe
int group = 0;
for (int j = 0; j < rowSummations[i].length; j++) {
if (randomNumber < rowSummations[i][j]) {
group = j;
break;
}
}
// Erster und letzter Eintrag von Gruppe bestimmen
// Erste Gruppe ist potentiell größer als die anderen
int first = group == 0 ? 0 : enlargementFirstGroup + colsPerGroup * group;
int last = group == 0 ? colsPerGroup - 1 + enlargementFirstGroup : first + colsPerGroup - 1;
// Suche Eintrag in Gruppe
double sum = group == 0 ? 0 : rowSummations[i][group - 1];
for (int j = first; j <= last; j++) {
sum += matrix[i][j];
if (randomNumber < sum) {
if (addedCols[j]) {
// ---------------------------------------------
// Bereits hinzugefügte Spalte erkannt, aber wie reagieren?
//
// Überlegung 1: So lange in Richtung des nächsten Nachbars gehen bis
// noch nicht verwendete Spalte erreicht
// -> Problem 1: Dann erhöht sich implizit die Wahrscheinlichkeit, dass die
// benachbarten Spalten genommen werden relativ zu den
// anderen noch zur Verfügung stehenden Spalten, obwohl an
// den Wahrscheinlichkeiten nichts verändert werden soll
// -> Problem 2: Umso weiter am Ende des Algos wir sind, umso
// unwahrscheinlicher wird es, direkt eine passende Spalte
// zu finden (-> Performance-Gewinn wird wieder zu nichte gemacht)
//
// Überlegung 2: Zufallszahl-Reichweite einschränken, so dass die Werte bereits
// hinzugefügter Spalten von der Reichweite abgezogen werden.
// -> Problem 1: Wie performant realisieren - ohne für alle noch ausstehenden
// Reihen mit einer Schleife die Subtraktions-Werte festzuhalten?
// -> Problem 2: Die Reichweiten der Gruppen stimmen dann nicht mehr. Da die
// Zufallszahlen immer kleiner würden, würde man immer häufiger in den
// niedrigen Gruppen landen (die Gruppen-Summen müssten also auch angepasst
// werden -> Performance?)
// ---------------------------------------------
}
result.setColForRow(i, j);
addedCols[j] = true; // NEU
break;
}
}
}
Wäre echt dankbar, falls jemand eine Idee hat?
Gruß
Zuletzt bearbeitet von einem Moderator: