SolvingMethod osztály

A különféle feladatok megoldására tucatnyi különféle algoritmust használunk. A keretprogram egyszerűsíthető, ha létrehozunk egy absztrakt osztályt, mely egyrészt a különféle algoritmusok közös részét tartalmazza, másrészt előírja az egyes algoritmusnál megvalósítandó metódusokat.

2.4. ábra - Absztrakt megoldást kereső algoritmus

Absztrakt megoldást kereső algoritmus

A feladatok megoldásának jellemző közös része az input és output. Ennek megfelelően igen sok osztályt kell ehhez importálunk.

package hu.unideb.inf.optimization.methods;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Scanner;
/**
 * Különféle megoldási módszerek közös absztakt őse.
 * @author ASZALÓS László
 */
public abstract class SolvingMethod<MyState extends State> { 

Ha más az adatszerkezetünk, akkor mást is kell outputként kiírni. Így a kiírást feladatonként más és más osztály segítségével oldjuk meg, melyet a főprogram paramétereként adhatunk majd meg:

/**
 * A feladat eredményeinek kiírása.
 */
public PrintSolution print;

Ehhez az attribútumhoz megalkotunk egy setter metódust is:

/**
 * Beállítja a kiírási módszert.
 * @param myPrint a kiírást megvalósító osztály
 */
public void setPrint(PrintSolution myPrint) {
    print = myPrint;
}

A különféle módszerek eltérő számú paraméterrel finomhangolhatóak. Ezeket meg lehetne adni a főprogram paramétereként is, viszont a tervezett nagy számú kísérlet kényelmes elvégzése érdekében ezeket külső állományokból fogjuk beolvasni. Mivel egy paraméter a program futása során nem változtatja meg az értékét, mi konstansként hivatkozunk rájuk a programunkban:

/** Betölti a módszer "konstansainak" értékét
 * @param filename konstansokat tartalmazó fájl
 * @throws FileNotFoundException
 */
public void initializeConstants(String filename, boolean print) {
    try {
        Scanner scanner = new Scanner(new FileInputStream(filename));
        manageConstants(scanner,print);
    } catch (FileNotFoundException e) {
        System.err.println("Cannot open " + filename);
        System.err.println(e.getMessage());
        System.exit(1);
    }
}

Az előbbi metódus a paramétereként megadott helyi fájlból olvasta be a konstansokat. Az alábbi metódus valamely honlapon szereplő állományból dolgozik. Ennek segítségével viszonylag könnyedén lehet a számítási feladatokat számítógépek között szétosztani, így felgyorsítani a hosszabb számításokat:

/** Betölti a módszer "konstansainak" értékét
 * @param address konstansokat tartalmazó URL
 * @throws FileNotFoundException
 */
public void initializeConstants(URL address, boolean print) {
    try {
        Scanner scanner = new Scanner(
                new InputStreamReader(address.openStream()));
        manageConstants(scanner, print);
    } catch (IOException ex) {
        System.err.println("Cannot read constants from " +
                address.toString());
    }
}

A konstansainkat tartalmazó fájl minden sorában pontosan egy konstans szerepel. Ez alól kivételt jelentenek a százalékjellel kezdődő sorok, melyeket az alábbi metódus figyelmen kívül hagy. Az állomány minden sorát beolvassa a rutin, így ha az konstanst tartalmaz, akkor a szóközök alapján felbontja részekre, és sorban található 2 vagy 3 adatot átadja a konstanst értelmező rutinnak. Annak vizsgálata, hogy a sor tartalmaz-e valóban legalább két adatot, vagy a második és harmadik adat egész számot jelöl-e, ebből a rutinból kimaradt, így ilyenkor a kivételkezelő állítja le a program futását:

/**
 * A konstansokat tartalmazó állomány minden sorát beolvassa és értelmezi.
 * @param scanner Konstansokat tartalmazó állomány
 * @param print kiírja-e a konstansokat a standart outputra?
 * @throws NumberFormatException 
 */
protected void manageConstants(Scanner scanner, boolean print) throws NumberFormatException {
    while (scanner.hasNextLine()) {
        String sor = scanner.nextLine();
        if (sor.length()>1 && sor.charAt(0) != '%') {
            if (print) {System.out.println("# " + sor);}
            String[] args = sor.split(" ");
            int numerator = Integer.parseInt(args[1]);
            if (args.length == 2) {
                this.constants(args[0], numerator, 1);
            } else {
                this.constants(args[0], numerator, Integer.parseInt(args[2]));
            }
        }
    }
}

A konstans feldolgozása, azaz, hogy azt melyik változóban tároljuk, az már a konkrét módszeren múlik. Így itt csak az absztrakt metódust adjuk meg, melynek első paramétere a konstans nevét, többi paramétere pedig annak értékét határozza meg:

/** Beállítja egy konstans értékét.
 * @param name konstans neve
 * @param numerator konstans értéke (számláló)
 * @param denominator tört szám esetén a nevező
 */
abstract public void constants(String name, int numerator, int denominator);

Legvégére maradt a lényegi rész, a megoldáskereső algoritmus váza. E generikus metódus miatt kellett az osztályt is generikusnak definiálni. A metódus megkap egy állapotot, és általa meghatározott legjobb állapottal tér vissza. A bemenő paraméterként szereplő állapot nem feltétlenül a kiinduló állapot, viszont arra jó, hogy az esetlegesen használt bonyolult adatszerkezeteket átadhassuk, arról szükség szerint másolatot készíthessünk.

/**
 * Végrehajtja az adott kereső algoritmust.
 * @return közel optimális megoldás
 */
public abstract MyState solve(MyState d);
}