Java: Schleifen & Rekursion

Aufgabenstellung: Eine ISBN-Nummer validieren.
Infos zur Berechnung hier

Zu aller erst einmal, wie funktioniert in Java die grundlegende Ein- und Ausgabe?

Eingabe Konsole

BufferedReader br = new BufferedReader( new InputStreamReader(System.in) );
String str = br.readLine();
System.out.println("Eingabe: " + str);


Eingabe aus Datei

BufferedReader br = new BufferedReader( new FileReader("input.txt"); );
String str = br.readLine();
System.out.println("Eingabe: " + str);


Egal ob Datei oder Konsole, die Funktionsaufrufe unterscheiden sich nur in dem BufferedReader übergebenen Argument.

Als nächstes Schleifen:
Prinzipiell lässt sich jede Schleife durch jede beliebige andere Schleife ersetzen:

*pseudocode*, ungetestet

while:

while(expr)
{
    doSomething();
}


Die wohl einfachste Schleife, while verdeutlicht das am Einfachsten:
Solange ein Ausdruck (expr = expression) wahr ist, wird der Block {} unten ausgeführt.

for:

for(int i = 0; i < 10; i++)
{
    doSomething();
}

Diese for-Schleife führt einen Block 10 mal aus. Im Unterschied zur while wird der Codeblock nur zehn mal ausgeführt.

for(initialise; expression; increment)

Genau betrachtet erspart die for-Schleife zwei Codezeilen, denn mit while realisiert sähe das so aus:

int i = 0; // initialise
while(i < 10) // expression
{
    doSomething();
    i++; // increment
}


Somit hängt es vom jeweiligen Fall ab, welches Schleifenkonstrukt für den Programmierer besser geeignet ist, demzufolge weniger Aufwand bedeutet.
Rekursion ist vorerst zurückgestellt, weiter mit

dem ternären Operator, auf deutsch auch dreifacher Operator genannt:

var = expr ? true : false

ist gleich mit

if(expr)
{
    var = true;
}
else
{
    var = false
};

Programmierer sind faul. Zu Gunsten der Lesbarkeit und Vermeidung von Redundanzen sollten diese Ausdrücke nicht zu lang werden.


Zurück zur Aufgabenstellung: Berechnung der Prüfsumme einer ISBN-Nummer

import java.io.*;
import java.util.*;

public class main {
    
    // Einlesen der Eingabe
    public static void main(String args[]) throws IOException
    {
        System.out.println("Test");
        FileReader fr = null;
        try
        {
            fr = new FileReader("input.txt");
//            BufferedReader br = new BufferedReader(fr); // read from file
        }
        catch(Exception e)
        {
            System.out.println("Fehler: Datei nicht gefunden.");
//            int dummy = System.in.read(); // pause - read a single character
//            return;
        }
        
        System.out.println("Bitte eine ISBN-Nummer eingeben");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        String str = br.readLine();
        System.out.println("Eingabe: " + str + " " + validateISBN(str));
        System.out.println();
        // 342701144 - 5
    }


    // Berechnung Prüfziffer ISBN
    public static boolean validateISBN(String ISBN)
    {
        int sum = 0, count = 0;
        char checkChar = 7; // BEL
        
        try
        {
            // Schleife
            for(count = 0; count <= 8; count++)
            {
                sum += (int) (ISBN.charAt(count) - 48) * (count+1);
//                System.out.println((int)ISBN.charAt(count) - 48);
            }
            
            // Ternärer Operator
            checkChar = (sum % 11) == 10 ? 'X' : (char) (sum % 11 + 48);
            
            // Prüfziffer Vergleich
            if(ISBN.length() == 10 && checkChar == ISBN.charAt(9))
            {
                return true;
            }
//            System.out.println(sum);
//            System.out.println("checkChar: " + checkChar);
        }
        catch(Exception e)
        {
            System.out.println("Fehler. Ungültige Eingabe");
        }
        return false;
    }
}


Kurze Funktionsreferenz





NameBeschreibung
string.charAt(x)Gibt von einem String den einzelnen Buchstaben an Stelle x zurück.
(cast) varUmwandlung von var in Datentyp cast.
string.lengthGibt Länge eines Strings zurück.


48 wird abgezogen, bzw. hinzugefügt, um aus einem Buchstabe aus dem ASCII-Zeichensatz wirklich eine Zahl zu erhalten, 0 beginnt dort bei Wert 48.

Die Schleife kann ich wie folgt umschreiben:

for(count = 0; count <= 8; count++)
{
    sum += (int) (ISBN.charAt(count) - 48) * (count+1);
}

while(count <= 8)
{
    sum += (int) (ISBN.charAt(count) - 48) * (count+1);
    count++;
}


Wie Anfangs erwähnt, kann jede Schleife durch eine beliebig andere ersetzt werden.
Nun zur Rekursion: Jede Schleife kann durch eine if-Abfrage ersetzt werden:

if(count <= 8)
{
    sum += (int) (ISBN.charAt(count) - 48) * (count+1);
    count++;
}

der Codeblock unten wird allerdings nur einmal ausgeführt.
Indem man das Ganze in eine Funktion packt:

public static void rTest()
{
    if(count <= 8)
    {
        sum += (int) (ISBN.charAt(count) - 48) * (count+1);
        count++;
        rTest();
    }
}

und zum Schluss erneut die eigene Funktion aufgerufen wird, kann ein Verhalten, ähnlich dem einer Schleife, erreicht werden.

Ein Problem besteht noch: Der Zustand der Variablen wird nicht übernommen. Dies kann über 2 Wege bewerkstelligt werden:

  • Deklaration von static Variablen in der Klasse, die ihren Wert auch nach Verlassen der Funktion/Methode beibehalten
  • Übergabe der aktuellen Werte als Funktionsparameter im Funktionsaufruf

Von Rekursion ist grundsätzlich abzuraten, so wie auch jeder von goto abraten wird.
Die Nachvollziehbarkeit, Übersichtlichkeit und das Debugging des Codes wird hierdurch erschwert.
Des Weiteren werden die Ressourcen erst nach dem letzten rekursiven Funktionsaufruf freigegeben, da der implizite Return aus der Funktion erst nach dem rekursiven Funktionsaufruf selbst erfolgt.

ISBN-Nummern Berechnung rekursiv:

public static boolean r_validateISBN(String ISBN)
{
    
    try
    {
        // Schleife
        if(count <= 8)
        {
            sum += (int) (ISBN.charAt(count) - 48) * (count+1);
            count++;
            r_validateISBN(ISBN);
//                System.out.println((int)ISBN.charAt(count) - 48);
        }

        
        // Ternärer Operator
        checkChar = (sum % 11) == 10 ? 'X' : (char) (sum % 11 + 48);
        
        // Prüfziffer Vergleich
        if(ISBN.length() == 10 && checkChar == ISBN.charAt(9))
        {
            return true;
        }
//            System.out.println(sum);
//            System.out.println("checkChar: " + checkChar);
    }
    catch(Exception e)
    {
        System.out.println("Fehler. Ungültige Eingabe");
    }
    return false;
}

in der Klasse sind noch

static int sum = 0, count = 0;
static char checkChar = 7; // BEL

hinzuzufügen.

In ihrem aktuellen Zustand kann die Klassenfunktion nur ein einziges mal erfolgreich ausgeführt werden, zumal sum niemals zurückgesetzt wird.


1 Jahr zuvor...

char _5_3(char * c, int type){
    int tmp = 0;
    int count = 0;
    char check = '0';
    switch (type){
    case 0: // ISBN 10 Stellen
        for (int i = 0; i < 9; i++){
            tmp += ((int)c[i] - 48) * (i + 1);
        }
        check = tmp % 11;
        if (check == 10){
            return 'X';
        }
        return (char)(check + 48);
        break;
    case 1: // EAN 13 Stellen
        for (int i = 0; i < 12; i++){
            if ((i % 2) == 0){
                tmp += ((int)c[i] - 48) * 1;
            }
            else
            {
                tmp += ((int)c[i] - 48) * 3;
            }
        }
        while (((tmp + count) % 10) != 0){
            count++;
        }
        return (char)(count + 48);
        break;
    }
}

war ich noch aktiv in C unterwegs.

Früher war alles besser? Welchen Vorteil bietet Java?
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

try
{
    
}
catch(Exception e)
{
    
}


Ist das Array zu klein, crash das Programm weg. Mit Java kann der Fehler gefangen werden.
Ferner muss ich mich nicht mehr um das Einlesen einer Datei kümmern. Oder das es keine Strings gibt.
Auf dem Mikrocontroller läuft's nicht...