commit ce5d70b3a8e36102c36bdf457e3d5238d355d636 Author: J. Neugebauer Date: Thu Nov 12 09:14:47 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fe8940 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# ---> Java +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt +Dokumente* + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + diff --git a/BinaryTree.java b/BinaryTree.java new file mode 100644 index 0000000..b589034 --- /dev/null +++ b/BinaryTree.java @@ -0,0 +1,210 @@ + /** + *

+ * Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018 + *

+ *

+ * Generische Klasse BinaryTree + *

+ *

+ * Mithilfe der generischen Klasse BinaryTree koennen beliebig viele + * Inhaltsobjekte vom Typ ContentType in einem Binaerbaum verwaltet werden. Ein + * Objekt der Klasse stellt entweder einen leeren Baum dar oder verwaltet ein + * Inhaltsobjekt sowie einen linken und einen rechten Teilbaum, die ebenfalls + * Objekte der generischen Klasse BinaryTree sind. + *

+ * + * @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule + * @version Generisch_03 2014-03-01 + */ +public class BinaryTree { + + /* --------- Anfang der privaten inneren Klasse -------------- */ + + /** + * Durch diese innere Klasse kann man dafuer sorgen, dass ein leerer Baum + * null ist, ein nicht-leerer Baum jedoch immer eine nicht-null-Wurzel sowie + * nicht-null-Teilbaeume, ggf. leere Teilbaeume hat. + */ + private class BTNode { + + private CT content; + private BinaryTree left, right; + + public BTNode(CT pContent) { + // Der Knoten hat einen linken und einen rechten Teilbaum, die + // beide von null verschieden sind. Also hat ein Blatt immer zwei + // leere Teilbaeume unter sich. + this.content = pContent; + left = new BinaryTree(); + right = new BinaryTree(); + } + + } + + /* ----------- Ende der privaten inneren Klasse -------------- */ + + private BTNode node; + + /** + * Nach dem Aufruf des Konstruktors existiert ein leerer Binaerbaum. + */ + public BinaryTree() { + this.node = null; + } + + /** + * Wenn der Parameter pContent ungleich null ist, existiert nach dem Aufruf + * des Konstruktors der Binaerbaum und hat pContent als Inhaltsobjekt und + * zwei leere Teilbaeume. Falls der Parameter null ist, wird ein leerer + * Binaerbaum erzeugt. + * + * @param pContent + * das Inhaltsobjekt des Wurzelknotens vom Typ ContentType + */ + public BinaryTree(ContentType pContent) { + if (pContent != null) { + this.node = new BTNode(pContent); + } else { + this.node = null; + } + } + + /** + * Wenn der Parameter pContent ungleich null ist, wird ein Binaerbaum mit + * pContent als Inhalt und den beiden Teilbaeume pLeftTree und pRightTree + * erzeugt. Sind pLeftTree oder pRightTree gleich null, wird der + * entsprechende Teilbaum als leerer Binaerbaum eingefuegt. So kann es also + * nie passieren, dass linke oder rechte Teilbaeume null sind. Wenn der + * Parameter pContent gleich null ist, wird ein leerer Binaerbaum erzeugt. + * + * @param pContent + * das Inhaltsobjekt des Wurzelknotens vom Typ ContentType + * @param pLeftTree + * der linke Teilbaum vom Typ BinaryTree + * @param pRightTree + * der rechte Teilbaum vom Typ BinaryTree + */ + public BinaryTree(ContentType pContent, BinaryTree pLeftTree, BinaryTree pRightTree) { + if (pContent != null) { + this.node = new BTNode(pContent); + if (pLeftTree != null) { + this.node.left = pLeftTree; + } else { + this.node.left = new BinaryTree(); + } + if (pRightTree != null) { + this.node.right = pRightTree; + } else { + this.node.right = new BinaryTree(); + } + } else { + // Da der Inhalt null ist, wird ein leerer BinarySearchTree erzeugt. + this.node = null; + } + } + + /** + * Diese Anfrage liefert den Wahrheitswert true, wenn der Binaerbaum leer + * ist, sonst liefert sie den Wert false. + * + * @return true, wenn der Binaerbaum leer ist, sonst false + */ + public boolean isEmpty() { + return this.node == null; + } + + /** + * Wenn pContent null ist, geschieht nichts.
+ * Ansonsten: Wenn der Binaerbaum leer ist, wird der Parameter pContent als + * Inhaltsobjekt sowie ein leerer linker und rechter Teilbaum eingefuegt. + * Ist der Binaerbaum nicht leer, wird das Inhaltsobjekt durch pContent + * ersetzt. Die Teilbaeume werden nicht geaendert. + * + * @param pContent + * neues Inhaltsobjekt vom Typ ContentType + */ + public void setContent(ContentType pContent) { + if (pContent != null) { + if (this.isEmpty()) { + node = new BTNode(pContent); + this.node.left = new BinaryTree(); + this.node.right = new BinaryTree(); + } + this.node.content = pContent; + } + } + + /** + * Diese Anfrage liefert das Inhaltsobjekt des Binaerbaums. Wenn der + * Binaerbaum leer ist, wird null zurueckgegeben. + * + * @return das Inhaltsobjekt der Wurzel vom Typ ContentType bzw. null, wenn + * der Binaerbaum leer ist + */ + public ContentType getContent() { + if (this.isEmpty()) { + return null; + } else { + return this.node.content; + } + } + + /** + * Falls der Parameter null ist, geschieht nichts. Wenn der Binaerbaum leer + * ist, wird pTree nicht angehaengt. Andernfalls erhaelt der Binaerbaum den + * uebergebenen BinaryTree als linken Teilbaum. + * + * @param pTree + * neuer linker Teilbaum vom Typ BinaryTree + */ + public void setLeftTree(BinaryTree pTree) { + if (!this.isEmpty() && pTree != null) { + this.node.left = pTree; + } + } + + /** + * Falls der Parameter null ist, geschieht nichts. Wenn der Binaerbaum leer + * ist, wird pTree nicht angehaengt. Andernfalls erhaelt der Binaerbaum den + * uebergebenen BinaryTree als rechten Teilbaum. + * + * @param pTree + * neuer linker Teilbaum vom Typ BinaryTree + */ + public void setRightTree(BinaryTree pTree) { + if (!this.isEmpty() && pTree != null) { + this.node.right = pTree; + } + } + + /** + * Diese Anfrage liefert den linken Teilbaum des Binaerbaumes. Wenn der + * Binaerbaum leer ist, wird null zurueckgegeben. + * + * @return linker Teilbaum vom Typ BinaryTree oder null, wenn + * der aktuelle Binaerbaum leer ist + */ + public BinaryTree getLeftTree() { + if (!this.isEmpty()) { + return this.node.left; + } else { + return null; + } + } + + /** + * Diese Anfrage liefert den rechten Teilbaum des Binaerbaumes. Wenn der + * Binaerbaum (this) leer ist, wird null zurueckgegeben. + * + * @return rechter Teilbaum vom Typ BinaryTree oder null, wenn + * der aktuelle Binaerbaum (this) leer ist + */ + public BinaryTree getRightTree() { + if (!this.isEmpty()) { + return this.node.right; + } else { + return null; + } + } + +} diff --git a/Classification.java b/Classification.java new file mode 100644 index 0000000..bcdf41f --- /dev/null +++ b/Classification.java @@ -0,0 +1,24 @@ +/** + * Blattknoten eines Entscheidungsbaums. Klassifiziert einen Datensatz, + * nachdem alle Entscheidungen des Pfades getroffen wurden. + */ +public class Classification extends DecisionNode { + + // Die Klassifikation als String + private String classification; + + /** + * Erstellt eine Klassifikation + * + * @param pClassification Name der Klasse (z.B. "Ja" oder "Nein") + */ + public Classification( String pClassification ) { + classification = pClassification; + } + + @Override + public String decide( Dataset pDataset ) { + return classification; + } + +} diff --git a/Dataset.java b/Dataset.java new file mode 100644 index 0000000..f8ed3aa --- /dev/null +++ b/Dataset.java @@ -0,0 +1,40 @@ +/** + * Ein Datensatz, der vom {@link DecisionTreeBuilder} im Entscheidungsbaum + * klassifiziert werden kann. + *

+ * Basiert auf einer {@link java.util.HashMap}. + */ +public class Dataset { + + // Speicher für die Daten + private java.util.HashMap data; + + /** + * Erzeugt einen leeren Datensatz + */ + public Dataset() { + data = new java.util.HashMap(); + } + + /** + * Gibt den Wert für ein Attribut in diesem Datensatz zurück oder + * null, wenn das Attribut im Datensatz nicht existiert. + * + * @param pAttribute Name des Attributs. + * @return Wert im Datensatz oder null. + */ + public String get( String pAttribute ) { + return data.getOrDefault(pAttribute, null); + } + + /** + * Setzt den Wert eines Attributs in diesem Datensatz. + * + * @param pAttribute Name des Attributs. + * @param pValue Neuer Wert des Attributs. + */ + public void set( String pAttribute, String pValue ) { + data.put(pAttribute, pValue); + } + +} diff --git a/Decision.java b/Decision.java new file mode 100644 index 0000000..d821b9b --- /dev/null +++ b/Decision.java @@ -0,0 +1,39 @@ +/** + * Innerer Knoten eines Entscheidungsbaums. Entscheidet basierend auf + * dem Wert eines Attributs eines {@link Dataset Datensatzes}, ob im linken oder + * rechten Teilbaum weitergesucht werden muss. + *

+ * Da es sich um binäre Entscheidungen handelt wird immer nur der Wert + * für den linken Teilbaum angegeben und für alle anderen Werte der rechte + * Teilbaum gewählt. + *

+ */ +public class Decision extends DecisionNode { + + // Name des Attributs + private String attribute; + + // Wert, bei dem links weitergesucht werden soll + private String valueLeft; + + /** + * Erstellt eine binäre Entscheidung. + * + * @param pAttribute Name des Attributs. + * @param pValueLeft Wert, wann im linken Teilbaum weitergesucht werden muss. + */ + public Decision( String pAttribute, String pValueLeft ) { + attribute = pAttribute; + valueLeft = pValueLeft; + } + + @Override + public String decide( Dataset pDataset ) { + if( pDataset.get(attribute).equals(valueLeft) ) { + return "left"; + } else { + return "right"; + } + } + +} diff --git a/DecisionNode.java b/DecisionNode.java new file mode 100644 index 0000000..169bc5d --- /dev/null +++ b/DecisionNode.java @@ -0,0 +1,19 @@ +/** + * Ein Knoten im Entscheidungsbaum. Knoten sind entweder {@link Decision Entscheidungen} + * (innere Knoten) oder {@link Classification Klassifikationen} (Blattknoten). + */ +public abstract class DecisionNode { + + /** + * Führt den Test der Entscheidung durch. Die Methode + * bekommt den Datensatz als Parameter und gibt einen + * von drei Strings zurück: + *
    + *
  1. "left": Folge dem linken Teilbaum
  2. + *
  3. "right": Folge dem rechten Teilbaum
  4. + *
  5. Die finale Klassifikation, wenn dies ein Blatt des Baumes ist (z.B. "yes" oder "no")
  6. + *
+ */ + public abstract String decide( Dataset pDataset ); + +} diff --git a/DecisionTreeBuilder.java b/DecisionTreeBuilder.java new file mode 100644 index 0000000..a8e1ef2 --- /dev/null +++ b/DecisionTreeBuilder.java @@ -0,0 +1,159 @@ +/** + * Enthält einen Entscheidungsbaum und kann {@link Dataset Datensätze} damit + * klassifizieren. + */ +public class DecisionTreeBuilder { + + // Wurzel des Entscheidungsbaums + private BinaryTree decisionTree; + + public DecisionTreeBuilder() { + // Vorbereiten der Klassifikationen (Blätter) + BinaryTree classYes = new BinaryTree<>( + new Classification("ja") + ); + BinaryTree classNo = new BinaryTree<>( + new Classification("nein") + ); + + + /*aufg* + // TODO: Hier den Entscheidungsbaum aufbauen. + // + // Der Baum wird von den Blättern nach "oben" zur Wurzel aufgebaut. + // z.B. + // BinaryTree wind = new BinaryTree<>( + // new Decision("wind", "stark"), // Entscheide Merkmal "wind", gehe links bei "stark" + // classNo, // Linker Teilbaum + // classYes // rechter Teilbaum + // ); + // usw... + *aufg*/ + //ml* + BinaryTree wind = new BinaryTree<>( + new Decision("wind", "stark"), + classNo, + classYes + ); + BinaryTree vorhersage = new BinaryTree<>( + new Decision("vorhersage", "sonnig"), + wind, + classYes + ); + BinaryTree feuchtigkeit = new BinaryTree<>( + new Decision("feuchtigkeit", "hoch"), + classNo, + classYes + ); + //*ml + // Die Wurzel zuletzt + decisionTree = new BinaryTree<>( + new Decision("vorhersage", "regnerisch"), + feuchtigkeit, + vorhersage + ); + } + + /** + * Schickt die {@link #getTestdata() Testdaten} durch den Entscheidungsbaum + * und gibt die Entscheidungen auf der Kommandozeile aus. + * + * @see #testDataset(Dataset) + */ + public void testTestdata() { + Dataset[] testdata = getTestdata(); + for( int i = 0; i < testdata.length; i++ ) { + Dataset d = testdata[i]; + String result = testDataset(d); + System.out.printf("(%s, %s, %s, %s) = %s", + d.get("vorhersage"), d.get("temperatur"), + d.get("feuchtigkeit"), d.get("wind"), + result); + System.out.println(); + } + } + + /** + * Schickt einen Datensatz durch den Entscheidungsbaum und gibt die + * Klasse als String zurück. + * + * @param pDataset Der zu testende Datensatz. + * @return Die Klasse, der der Datensatz zugeordnet wurde. + */ + public String testDataset( Dataset pDataset ) { + BinaryTree node = decisionTree; + + String result = ""; + while( result.equals("") || result.equals("left") + || result.equals("right") ) { + /*aufg* 1 + // TODO: Impementiere den Durchlauf durch den Entschiedungsbaum, + // indem bei jedem inneren Knoten die Entscheidung getroffen wird, + // ob links oder rechts weitergemacht wird. + *aufg*/ + /*aufg* 2 + // TODO: Impementiere den Durchlauf durch den Entschiedungsbaum, + // indem bei jedem inneren Knoten die Entscheidung getroffen wird, + // ob links oder rechts weitergemacht wird. + + DecisionNode e = null; + result = "???"; + + if( result.equals("left") ) { + // ... + } else if( result.equals("right") ) { + // ... + } + *aufg*/ + //ml* + DecisionNode e = node.getContent(); + result = e.decide(pDataset); + + if( result.equals("left") ) { + // Im linken Teilbaum weiter + node = node.getLeftTree(); + } else if( result.equals("right") ) { + // Im rechten Teilbaum weiter + node = node.getRightTree(); + } + //*ml + } + return result; + } + + /** + * Erstellt ein Array mit Test-Datensätzen für den Entscheidungsbaum. + * + * @return + */ + public Dataset[] getTestdata() { + String[][] testdata = { + {"sonnig", "heiß", "hoch", "schwach"}, + {"sonnig", "heiß", "hoch", "stark"}, + {"bewölkt", "heiß", "hoch", "schwach"}, + {"regnerisch", "mild", "hoch", "schwach"}, + {"regnerisch", "kalt", "normal", "schwach"}, + {"regnerisch", "kalt", "normal", "stark"}, + {"bewölkt", "mild", "hoch", "stark"}, + {"sonnig", "mild", "hoch", "schwach"}, + {"sonnig", "kalt", "normal", "schwach"}, + {"regnerisch", "mild", "normal", "schwach"}, + {"sonnig", "mild", "normal", "stark"}, + {"bewölkt", "heiß", "normal", "schwach"}, + {"bewölkt", "kalt", "normal", "stark"}, + {"regnerisch", "mild", "hoch", "stark"} + }; + + Dataset[] data = new Dataset[testdata.length]; + for( int i = 0; i < testdata.length; i++ ) { + data[i] = new Dataset(); + data[i].set("vorhersage", testdata[i][0]); + data[i].set("temperatur", testdata[i][1]); + data[i].set("feuchtigkeit", testdata[i][2]); + data[i].set("wind", testdata[i][3]); + } + + return data; + } + +} diff --git a/DecisionTreeTest.java b/DecisionTreeTest.java new file mode 100644 index 0000000..d6cb26a --- /dev/null +++ b/DecisionTreeTest.java @@ -0,0 +1,74 @@ + +import static org.junit.Assert.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class DecisionTreeTest { + + private static final String[][] known_data = { + {"sonnig", "heiß", "hoch", "schwach", "ja"}, + {"sonnig", "heiß", "hoch", "stark", "nein"}, + {"bewölkt", "heiß", "hoch", "schwach", "ja"}, + {"regnerisch", "mild", "hoch", "schwach", "nein"}, + {"regnerisch", "kalt", "normal", "schwach", "ja"}, + {"regnerisch", "kalt", "normal", "stark", "ja"}, + {"bewölkt", "mild", "hoch", "stark", "ja"}, + {"sonnig", "mild", "hoch", "schwach", "ja"}, + {"sonnig", "kalt", "normal", "schwach", "ja"}, + {"regnerisch", "mild", "normal", "schwach", "ja"}, + {"sonnig", "mild", "normal", "stark", "nein"}, + {"bewölkt", "heiß", "normal", "schwach", "ja"}, + {"bewölkt", "kalt", "normal", "stark", "ja"}, + {"regnerisch", "mild", "hoch", "stark", "nein"} + }; + + private static final String[][] unknown_data = { + {"regnerisch", "heiß", "hoch", "schwach", "nein"}, + {"sonnig", "kalt", "hoch", "stark", "nein"} + }; + + private DecisionTreeBuilder tree; + + private Dataset[] known_datasets, unknown_datasets; + + @Before + public void setUp() { + known_datasets = new Dataset[known_data.length]; + for( int i = 0; i < known_data.length; i++ ) { + known_datasets[i] = new Dataset(); + known_datasets[i].set("vorhersage", known_data[i][0]); + known_datasets[i].set("temperatur", known_data[i][1]); + known_datasets[i].set("feuchtigkeit", known_data[i][2]); + known_datasets[i].set("wind", known_data[i][3]); + } + + unknown_datasets = new Dataset[unknown_data.length]; + for( int i = 0; i < unknown_data.length; i++ ) { + unknown_datasets[i] = new Dataset(); + unknown_datasets[i].set("vorhersage", unknown_data[i][0]); + unknown_datasets[i].set("temperatur", unknown_data[i][1]); + unknown_datasets[i].set("feuchtigkeit", unknown_data[i][2]); + unknown_datasets[i].set("wind", unknown_data[i][3]); + } + + tree = new DecisionTreeBuilder(); + } + + @Test + public void testKnownData() { + for( int i = 0; i < known_datasets.length; i++ ) { + String result = tree.testDataset(known_datasets[i]); + assertEquals(known_data[i][4], result); + } + } + + @Test + public void testUnknownData() { + for( int i = 0; i < unknown_datasets.length; i++ ) { + String result = tree.testDataset(unknown_datasets[i]); + assertEquals(unknown_data[i][4], result); + } + } + +} diff --git a/README.TXT b/README.TXT new file mode 100644 index 0000000..0ad193f --- /dev/null +++ b/README.TXT @@ -0,0 +1,14 @@ +------------------------------------------------------------------------ +Dies ist die README-Datei des Projekts. Hier sollten Sie Ihr Projekt +beschreiben. +Erzählen Sie dem Leser (jemand, der nichts über dieses Projekt weiss), +alles, was er/sie wissen muss. Üblicherweise sollte der Kommentar +zumindest die folgenden Angaben umfassen: +------------------------------------------------------------------------ + +PROJEKTBEZEICHNUNG: +PROJEKTZWECK: +VERSION oder DATUM: +WIE IST DAS PROJEKT ZU STARTEN: +AUTOR(EN): +BENUTZERHINWEISE: diff --git a/package.bluej b/package.bluej new file mode 100644 index 0000000..460a419 --- /dev/null +++ b/package.bluej @@ -0,0 +1,91 @@ +#BlueJ package file +dependency1.from=DecisionNode +dependency1.to=Dataset +dependency1.type=UsesDependency +dependency2.from=Classification +dependency2.to=Dataset +dependency2.type=UsesDependency +dependency3.from=Decision +dependency3.to=Dataset +dependency3.type=UsesDependency +dependency4.from=DecisionTreeBuilder +dependency4.to=BinaryTree +dependency4.type=UsesDependency +dependency5.from=DecisionTreeBuilder +dependency5.to=DecisionNode +dependency5.type=UsesDependency +dependency6.from=DecisionTreeBuilder +dependency6.to=Classification +dependency6.type=UsesDependency +dependency7.from=DecisionTreeBuilder +dependency7.to=Decision +dependency7.type=UsesDependency +dependency8.from=DecisionTreeBuilder +dependency8.to=Dataset +dependency8.type=UsesDependency +editor.fx.0.height=722 +editor.fx.0.width=800 +editor.fx.0.x=388 +editor.fx.0.y=50 +objectbench.height=66 +objectbench.width=1201 +package.divider.horizontal=0.6 +package.divider.vertical=0.8983286908077994 +package.editor.height=622 +package.editor.width=1078 +package.editor.x=39 +package.editor.y=24 +package.frame.height=776 +package.frame.width=1241 +package.numDependencies=8 +package.numTargets=6 +package.showExtends=true +package.showUses=true +project.charset=UTF-8 +readme.height=58 +readme.name=@README +readme.width=47 +readme.x=10 +readme.y=10 +target1.height=40 +target1.name=Classification +target1.showInterface=false +target1.type=ClassTarget +target1.width=210 +target1.x=40 +target1.y=460 +target2.height=50 +target2.name=Decision +target2.showInterface=false +target2.type=ClassTarget +target2.width=190 +target2.x=300 +target2.y=460 +target3.height=120 +target3.name=BinaryTree +target3.showInterface=false +target3.type=ClassTarget +target3.width=440 +target3.x=890 +target3.y=250 +target4.height=40 +target4.name=Dataset +target4.showInterface=false +target4.type=ClassTarget +target4.width=270 +target4.x=430 +target4.y=30 +target5.height=50 +target5.name=DecisionNode +target5.showInterface=false +target5.type=AbstractTarget +target5.width=110 +target5.x=210 +target5.y=290 +target6.height=60 +target6.name=DecisionTreeBuilder +target6.showInterface=false +target6.type=ClassTarget +target6.width=270 +target6.x=580 +target6.y=230