From ce5d70b3a8e36102c36bdf457e3d5238d355d636 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 12 Nov 2020 09:14:47 +0100 Subject: [PATCH] Initial commit --- .gitignore | 26 +++++ BinaryTree.java | 210 +++++++++++++++++++++++++++++++++++++++ Classification.java | 24 +++++ Dataset.java | 40 ++++++++ Decision.java | 39 ++++++++ DecisionNode.java | 19 ++++ DecisionTreeBuilder.java | 159 +++++++++++++++++++++++++++++ DecisionTreeTest.java | 74 ++++++++++++++ README.TXT | 14 +++ package.bluej | 91 +++++++++++++++++ 10 files changed, 696 insertions(+) create mode 100644 .gitignore create mode 100644 BinaryTree.java create mode 100644 Classification.java create mode 100644 Dataset.java create mode 100644 Decision.java create mode 100644 DecisionNode.java create mode 100644 DecisionTreeBuilder.java create mode 100644 DecisionTreeTest.java create mode 100644 README.TXT create mode 100644 package.bluej 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