package jcgp.backend.modules.problem; import java.io.File; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import jcgp.backend.parsers.TestCaseParser; import jcgp.backend.resources.ModifiableResources; import jcgp.backend.resources.Resources; /** * Abstract model for a problem that uses test cases. A test case * problem is any problem that compares the chromosome output to * an expected output taken from a table of input-output mappings. *

* This class defines a basic data type for storing test cases, * TestCase, and provides core functionality to add and manipulate * test cases in the problem. A subclass of {@code TestCaseProblem} * must simply override {@code parseTestCase()} to convert parsed * problem data strings into the required data type (T). * * @see Problem * @author Eduardo Pedroni * @param the data type to be used by the TestCaseProblem. */ public abstract class TestCaseProblem extends Problem { /** * Basic data type for encapsulating test cases, it simply * contains arrays of inputs and outputs and associated getters. * * @author Eduardo Pedroni * @param */ public static class TestCase { private U[] inputs; private U[] outputs; /** * Creates a new test case, inputs and outputs * must be specified upon instantiation. * * @param inputs the array of inputs. * @param outputs the array of outputs. */ public TestCase(U[] inputs, U[] outputs) { this.inputs = inputs; this.outputs = outputs; } /** * @param index the index to return. * @return the indexed input. */ public U getInput(int index) { return inputs[index]; } /** * @param index the index to return. * @return the indexed output. */ public U getOutput(int index) { return outputs[index]; } /** * @return the complete array of inputs. */ public U[] getInputs() { return inputs; } /** * @return the complete array of outputs. */ public U[] getOutputs() { return outputs; } } protected ObservableList> testCases; protected Resources resources; /** * Creates a new TestCaseProblem object. * * @param resources a reference to the experiment's resources. */ public TestCaseProblem(Resources resources) { super(); this.resources = resources; testCases = FXCollections.observableArrayList(); } /** * For internal use only, this method computes and returns the maximum fitness * based on the number of test cases. Subclasses should override this method * as necessary. * * @return the maximum fitness based on number of test cases. */ protected double getMaxFitness() { int fitness = 0; for (TestCase tc : testCases) { fitness += tc.getOutputs().length; } return fitness; } /** * @return a list containing the test cases. */ public ObservableList> getTestCases() { return testCases; } /** * This method is used internally by {@code addTestCase()} in order * to appropriately parse strings into the right data type for the * test cases. Since the data type is problem-dependent, subclasses must * implement this method. This method must return a built {@code TestCase} * object from the arguments given. * * @param inputs the inputs represented as strings. * @param outputs the outputs represented as strings. * @return the parsed test case. */ protected abstract TestCase parseTestCase(String[] inputs, String[] outputs); /** * Adds test cases to the problem instance as they get parsed from a * problem data file. This template method uses {@code parseTestCase}, which * must be implemented by subclasses. * * @param inputs the inputs represented as strings. * @param outputs the outputs represented as strings. */ public final void addTestCase(String[] inputs, String[] outputs) { TestCase testCase = parseTestCase(inputs, outputs); if (testCase.getInputs().length != resources.inputs()) { throw new IllegalArgumentException("Received test case with " + testCase.getInputs().length + " inputs but need exactly " + resources.inputs()); } else if (testCase.getOutputs().length != resources.outputs()) { throw new IllegalArgumentException("Received test case with " + testCase.getOutputs().length + " outputs but need exactly " + resources.outputs()); } else { this.testCases.add(testCase); maxFitness.set(getMaxFitness()); } } /** * Remove all test cases. */ public void clearTestCases() { testCases.clear(); maxFitness.set(getMaxFitness()); } @Override public void parseProblemData(File file, ModifiableResources resources) { // use standard test case parser for this TestCaseParser.parse(file, this, resources); } }