// Autor: Martin Heinrich
// MatNr: 0125222
// BspNr: 1118
// Titel: Gleichung mit drei Variablen
// Beschreibung: Ein lineares inhomogenes Gleichungssystem von drei Gleichungen in drei Unbekannten soll gelöst werden.
// Eingabe: 12 integer-Werte (je 4 Werte pro Gleichung: 3 Koeffizienten und eine Konstante als Ergebnis)
// Ausgabe: 3 float-Werte
// Fehlerbehandlung: alle Werte lesen, einmalige Meldung mit Zeilenvorschub, keine weitere Berechnung
// Fehlermeldungen: "?" bei ungültigen Datentypen, "FALSCHE EINGABE" bei inhaltlichen Fehlern

import eprog.*; // Verwendung des EPROG-Packages

public class dreigl extends EprogIO // In der Spezifikation ist der Dateiname klein geschrieben!
{                                   // Methoden der EprogIO können ohne Klassenangabe verwendet werden


//****************************************************************
  public static boolean getEquations(byte count, int[][] matrix)
  // getEquations liest count*(count+1) integer-Werte in die int-Matrix ein.
  // Im Fall eines ungültigen Datentyps liefert sie false, im Erfolgsfall true.
  // count gibt die Zahl der Gleichungen an.
  // matrix enthält im Erfolgsfall die Werte (1. Dimension entspricht den Zeilen); muss initialisiert sein!
  // Der Datentyp float ist für die weitere Berechnung in der Klasse dreigl erforderlich.
  {
    boolean retVal = true; // Rückgabewert der Methode (false sollte der Ausnahmefall sein)

    for (byte line = 0; line < count; line++) // Zeilenweises Einlesen (count mal)
      for (byte col = 0; col <= count; col++) // Spalten einer Zeile (count+1 mal)
      {
        try // Fehlerbehandlung
        {
          matrix[line][col] = readInt();
        } // try
        catch (EprogException e) {retVal = false;}; // Es wird trotzdem weitergelesen
      } // for (col)
    ; // for (line)
    return retVal;
  } // getEquations
//****************************************************************

//****************************************************************
  public static boolean isInhomogen(int[][] matrix, byte column)
  // isInhomogen überprüft, ob zumindest einer der Werte in der count-ten Spalte von 0 verschieden ist.
  // Falls alle Werte der count-ten Spalte (dh. 2. Dimension [count]) 0 sind, liefert sie false, sonst true
  // Die Methode verwendet keine Fehlerbehandlung, dh. das array sollte entsprechend groß sein!
  {
    int lines = matrix.length; // Length soll nur einmal aufgerufen werden

    for (int line = 0; line < lines; line++)
    {
      if (matrix[line][column] != 0)
      {
	      return true; // Beim ersten Wert != 0 ist die Bedingung bereits erfüllt
      }; // if (matrix != 0)
    }; // for (line)
    return false;
  }; // isInhomogen
//****************************************************************

//****************************************************************
  public static int checkValues(int[][] matrix, byte rangeMin, byte rangeMax)
  // checkValues überprüft die Koeffizienten auf den vorgegebenen Wertebereich (Grenzen eingeschlossen)
  // Der Rückgabewert ist 0, wenn alle Koeffizienten in dem geschlossenen Intervall [rangeMin, rangeMax] liegen.
  // Falls rangeMin > rangeMax oder ein Fehler auftritt wird der Wert -1 ausgegeben.
  // Ansonsten wird die Nummer (zeilenweise, dh 1. Dimension ist äußere Schleife) des verletzenden Koeffizienten ausgegeben.
  // matrix muss ein zweidimensionales, rechteckiges (gleich lange Zeilen) Datenfeld enthalten.
  // rangeMin/-Max geben das geschlossene Intervall vor (rangeMin stellt die Untergrenze dar).
  {
    int lines; // Mächtigkeit der 1. Dimension (um ein wiederholtes Abfragen von length zu verhindern).
    int cols; // Mächtigkeit der 2. Dimension.

    try // Fehlerbehandlung
    {
      if (rangeMin > rangeMax) {return -1;}; // Im catch-Fall wird -1 zurückgegeben.
      lines = matrix.length; // Initialisierung im try-Block (das Array könnte leer sein! -> Fehler)
      cols = matrix[0].length; // Initialisierung nur über 1. Zeile, aber Fehler werden sowieso abgefangen.
      for (int line = 0; line < lines; line++) // Durchlaufen aller Zeilen
        for (int col = 0; col < cols; col++) // Durchlaufen aller Spalten
        {
          if ((matrix[line][col] < rangeMin) || (matrix[line][col] > rangeMax))
          // "Nicht im Bereich" heißt "kleiner als Unter- oder größer als Obergrenze"
          {
            return (line * cols + col + 1);
          }; // if ("Nicht im Bereich")
        } // for (col)
      ; // for (line)
    } // try
    catch (Exception e) {return -1;};
    return 0;
  } // checkValues
//****************************************************************

//****************************************************************
  public static boolean solveEquations(byte count, int[][] matrix, float[] solutions)
  // solveEquations löst das Gleichungssystem, das durch count und matrix angegeben wird.
  // Falls das System nicht eindeutig lösbar ist (oder ein Fehler auftritt), liefert sie false, im Erfolgsfall true.
  // count gibt die Zahl der Gleichungen an, muss >= 2 sein!
  // matrix muss ein zumindest count entsprechendes Array mit Koeffizienten sein.
  // matrix wird durch den Rechenvorgang verändert!
  // solutions enthält im Erfolgsfall die Lösungen; muss initialisiert sein (zumindest count Elemente)
  // ALGORITHMUS: Die Matrix wird auf Halbdiagonalform gebracht (näheres siehe dreigl.txt).
  {
    int[] tempVar; // 1. Referenz zum Tauschen zweier Zeilen
                   // 2. Referenz um mehrfaches Berechnen des Index zu verhindern
    int factorBottom; // Erster Koeffizient der zu ersetzenden Zeile = Faktor für die subtrahierte Zeile
    int factorCurrent; // Erster Koeffizient der subtrahierten Zeile = Faktor für die zu ersetzende Zeile
    
    try // Fehlerbehandlung: Bei Unlösbarkeit wird ein Fehler erzeugt und alle anderen Anweisungen übersprungen.
    {
      for (byte countDown = count; countDown > 1; countDown--)
      // Zurückführen des Gleichungssystems auf eine Gleichung in einer Variable
      {
        if (matrix[countDown - 1][count - countDown] == 0) // Wenn der erste Koeffizient 0 ist,
        // muss eine andere Gleichung verwendet werden um das System zu reduzieren.
        {
          for (byte anyEqu = (byte)(countDown - 2); anyEqu >= -1; anyEqu--)
          // Suchen einer Gleichung mit erstem Koeffizienten != 0; Wenn keine gültige Zeile
          // gefunden wird tritt später ein Fehler auf (Zugriff auf matrix[-1]) -> unlösbar!
          {
            if (matrix[anyEqu][count - countDown] != 0)
            // In diesem Fall, kann die aktuelle Zeile mit der unbrauchbaren vertauscht werden.
            {
              tempVar = matrix[anyEqu];
              matrix[anyEqu] = matrix[countDown - 1];
              matrix[countDown - 1] = tempVar;
              break; // Es genügt, eine gültige Zeile zu finden
            }; // if (matrix != 0)
          }; // for (anyEqu)
        }; // if (matrix == 0)
        for (byte allEqu = (byte)(countDown - 2); allEqu >= 0; allEqu--)
        // Modifizieren ALLER anderen Gleichungen um erste Koeffizienten =0 zu erhalten
        {
          factorBottom = matrix[allEqu][count - countDown];
          factorCurrent = matrix[countDown - 1][count - countDown];
          matrix[allEqu][count - countDown] = 0; // Die Berechnung in der Schleife wäre überflüssig.

          tempVar = matrix[countDown - 1];
          for (byte allCol = (byte)(count - countDown + 1); allCol <= count; allCol++)
          // ALLE Koeffizienten werden modifiziert.
          {
            matrix[allEqu][allCol] *= factorCurrent;
            matrix[allEqu][allCol] -= factorBottom * tempVar[allCol];
          } // for (allCol)
        } // for (allEqu)
      }; // for (countDown)

// Ab hier enthält jede (i-te) Zeile count-i führende Nullen. Die erste Variable kann in der ersten Zeile
// abgelesen werden, durch Einsetzen können die restlichen ermittelt werden:
      if (matrix[0][count - 1] == 0) {return false;};
      solutions[count - 1] = (float)matrix[0][count] / matrix[0][count - 1]; // erste Zeile
      for (byte getSol = (byte)(count - 2); getSol >= 0; getSol--) // alle Gleichungen
      // Zuerst alle schon vorhandenen Lösungen einsetzen und "auf die rechte Seite bringen":
      {
        tempVar = matrix[count - getSol - 1];
        solutions[getSol] = tempVar[count]; // Beim Einsetzen der Lösungen kann der Typ integer verlassen werden ->
                                            // -> Berechnungen in solutions (Typ float)
        for (byte allCol = (byte)(getSol + 1); allCol < count; allCol++)
        // Alle Koeffizienten != 0 außer dem ersten werden durchlaufen
        {
          solutions[getSol] -= tempVar[allCol] * solutions[allCol];
        }; // for (allCol)
        // Danach "rechte Seite durch linke Seite"; BEACHTE: 0*x=k -> nicht (eindeutig) lösbar -> Fehler
        if (tempVar[getSol] == 0) {return false;};
        solutions[getSol] /= tempVar[getSol];
      }; // for (getSol)
    } // try
    catch (Exception e) {return false;};
    return true;
  } // solveEquations
//****************************************************************

//****************************************************************
  public static boolean printArrayFixedln(float[] data)
  // printArrayFixedln gibt die Werte von data mit EprogIO.printFixed durch je 
  // ein Leerzeichen getrennt aus und fügt einen Zeilenvorschub an.
  // Falls ein Fehler auftritt, liefert sie false, im Erfolgsfall true.
  // Klassenintern (beim Aufruf in dreigl.main) kann vom Erfolgsfall ausgegangen werden.
  // data enthält eine beliebige Anzahl von float-Werten (zumindest 2, da sonst ein Fehler auftritt)
  {
    String spaceChar = " "; // Benannte Konstante, weil ich nicht weiß ob sonst in jeder Schleifen-
                            // ausführung ein String (Klasse) erzeugt werden muss!
    int dataLength; // Anzahl der Elemente von data (um ein wiederholtes Abfragen von length zu verhindern).
                    // dataLength wird um 1 verkleinert, weil nach dem letzten Wert kein Leerzeichen
                    // sondern ein Zeilenvorschub folgt
    
    try // Fehlerbehandlung (klassenintern dürfte hier kein Fehler auftreten)
    {
      dataLength = data.length - 1; // Initialisierung im try-Block (das array könnte leer sein! -> Fehler)
      for (int loop = 0; loop < dataLength; loop++) // Läuft vom ersten zum vorletzten Element
      {
        printFixed(data[loop]); // Gibt den entsprechenden Wert aus.
        print(spaceChar); // Gibt das Leerzeichen zwischen den Werten aus.
      }; // for (loop)
      printFixed(data[dataLength]); println(); // Gibt den letzten Wert und einen Zeilenvorschub aus.
                                               // printFixedln hat nicht funktioniert!
    } // try
    catch (Exception e) {return false;};
    return true;
  } // printArrayFixedln
//****************************************************************

//****************************************************************
  public static void main(String[] args)
  // Das Hauptprogramm entält nur die Aufrufe und die Ausgabe von Fehlermeldungen
  {
    final byte equCount = 3; // Anzahl der Gleichungen bzw. Variablen
    final byte rangeMin = -20; // Kleinster erlaubter Eingabewert
    final byte rangeMax = 20; // Größster erlaubter Eingabewert
      // ACHTUNG: Beim Vergrößern des erlaubten Bereichs und/oder des Gleichungssystems (equCount) sollten
      // mögliche Überläufe beim Berechnen zumindest dokumentiert werden.
    String msgTypeErr = "?"; // Fehlermeldung bei ungültigem Datentyp
    String msgValErr = "FALSCHE EINGABE"; // Fehlermeldung bei inhaltlich falscher Eingabe
    
    int[][] matrix = new int[equCount][equCount+1]; // Enthält die Gleichungen (Koeffizienten)
                       // Das Feld hat in allen Prozeduren den selben Namen, auch wenn es unterschiedliche Referenzen sind.
                       // Das array wird sofort erstellt.
    float[] solutions; // Enthält die Lösungen
                       // Das Feld hat in allen Prozeduren den selben Namen, auch wenn es unterschiedliche Referenzen sind.

    if (getEquations(equCount, matrix))
    // Die Werte sollen eingelesen werden, Berechnungen werden nur im Erfolgsfall durchgeführt.
    {
      solutions = new float[equCount]; // Das array muss außerhalb von solveEquations erstellt werden.
      if (isInhomogen(matrix, equCount) && (checkValues(matrix, rangeMin, rangeMax) == 0) && solveEquations(equCount, matrix, solutions))
      // Die Ausgabe der Lösungen erfolgt, wenn das Gleichungssystem inhomogen ist UND
      // alle Koeffizienten im erlaubten Intervall liegen UND
      // der Lösungsalgorithmus zum Erfolg führt.
      // BEACHTE: solveEquations wird nur aufgerufen, wenn alle Werte erlaubt sind ("short circuiting").
      {
        printArrayFixedln(solutions); // Ausgabe der Lösungen im Erfolgsfall
      } // if (isInhomogen) && (checkValues) && (solveEquations)
      else
      { // if !(isInhomogen && checkValues && solveEquations)
        println(msgValErr); // Ausgabe der Meldung bei inhaltlichen Fehlern/Unlösbarkeit
      }; // else (isInhomogen) && (checkValues) && (solveEquations)
    } // if (getEquations)
    else
    { // if !getEquations
      println(msgTypeErr); // Ausgabe der Meldung bei ungültigem Datentyp
    } // else (getEquations)
  } // main
//****************************************************************  

} // dreigl