Initial commit

This commit is contained in:
ngb 2020-11-12 09:14:47 +01:00
commit ce5d70b3a8
10 changed files with 696 additions and 0 deletions

26
.gitignore vendored Normal file
View File

@ -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*

210
BinaryTree.java Normal file
View File

@ -0,0 +1,210 @@
/**
* <p>
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
* </p>
* <p>
* Generische Klasse BinaryTree<ContentType>
* </p>
* <p>
* 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.
* </p>
*
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
* @version Generisch_03 2014-03-01
*/
public class BinaryTree<ContentType> {
/* --------- 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<CT> {
private CT content;
private BinaryTree<CT> 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<CT>();
right = new BinaryTree<CT>();
}
}
/* ----------- Ende der privaten inneren Klasse -------------- */
private BTNode<ContentType> 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<ContentType>(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<ContentType>
* @param pRightTree
* der rechte Teilbaum vom Typ BinaryTree<ContentType>
*/
public BinaryTree(ContentType pContent, BinaryTree<ContentType> pLeftTree, BinaryTree<ContentType> pRightTree) {
if (pContent != null) {
this.node = new BTNode<ContentType>(pContent);
if (pLeftTree != null) {
this.node.left = pLeftTree;
} else {
this.node.left = new BinaryTree<ContentType>();
}
if (pRightTree != null) {
this.node.right = pRightTree;
} else {
this.node.right = new BinaryTree<ContentType>();
}
} 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. <br />
* 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<ContentType>(pContent);
this.node.left = new BinaryTree<ContentType>();
this.node.right = new BinaryTree<ContentType>();
}
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<ContentType>
*/
public void setLeftTree(BinaryTree<ContentType> 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<ContentType>
*/
public void setRightTree(BinaryTree<ContentType> 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<ContentType> oder null, wenn
* der aktuelle Binaerbaum leer ist
*/
public BinaryTree<ContentType> 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<ContentType> oder null, wenn
* der aktuelle Binaerbaum (this) leer ist
*/
public BinaryTree<ContentType> getRightTree() {
if (!this.isEmpty()) {
return this.node.right;
} else {
return null;
}
}
}

24
Classification.java Normal file
View File

@ -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;
}
}

40
Dataset.java Normal file
View File

@ -0,0 +1,40 @@
/**
* Ein Datensatz, der vom {@link DecisionTreeBuilder} im Entscheidungsbaum
* klassifiziert werden kann.
* <p>
* Basiert auf einer {@link java.util.HashMap}.
*/
public class Dataset {
// Speicher für die Daten
private java.util.HashMap<String, String> data;
/**
* Erzeugt einen leeren Datensatz
*/
public Dataset() {
data = new java.util.HashMap<String, String>();
}
/**
* Gibt den Wert für ein Attribut in diesem Datensatz zurück oder
* <code>null</code>, wenn das Attribut im Datensatz nicht existiert.
*
* @param pAttribute Name des Attributs.
* @return Wert im Datensatz oder <code>null</code>.
*/
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);
}
}

39
Decision.java Normal file
View File

@ -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.
* <p>
* Da es sich um <em>binäre Entscheidungen</em> handelt wird immer nur der Wert
* für den linken Teilbaum angegeben und für alle anderen Werte der rechte
* Teilbaum gewählt.
* </p>
*/
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";
}
}
}

19
DecisionNode.java Normal file
View File

@ -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:
* <ol>
* <li>"left": Folge dem linken Teilbaum</li>
* <li>"right": Folge dem rechten Teilbaum</li>
* <li>Die finale Klassifikation, wenn dies ein Blatt des Baumes ist (z.B. "yes" oder "no")</li>
* </ol>
*/
public abstract String decide( Dataset pDataset );
}

159
DecisionTreeBuilder.java Normal file
View File

@ -0,0 +1,159 @@
/**
* Enthält einen Entscheidungsbaum und kann {@link Dataset Datensätze} damit
* klassifizieren.
*/
public class DecisionTreeBuilder {
// Wurzel des Entscheidungsbaums
private BinaryTree<DecisionNode> decisionTree;
public DecisionTreeBuilder() {
// Vorbereiten der Klassifikationen (Blätter)
BinaryTree<DecisionNode> classYes = new BinaryTree<>(
new Classification("ja")
);
BinaryTree<DecisionNode> 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<DecisionNode> wind = new BinaryTree<>(
// new Decision("wind", "stark"), // Entscheide Merkmal "wind", gehe links bei "stark"
// classNo, // Linker Teilbaum
// classYes // rechter Teilbaum
// );
// usw...
*aufg*/
//ml*
BinaryTree<DecisionNode> wind = new BinaryTree<>(
new Decision("wind", "stark"),
classNo,
classYes
);
BinaryTree<DecisionNode> vorhersage = new BinaryTree<>(
new Decision("vorhersage", "sonnig"),
wind,
classYes
);
BinaryTree<DecisionNode> 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<DecisionNode> 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;
}
}

74
DecisionTreeTest.java Normal file
View File

@ -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);
}
}
}

14
README.TXT Normal file
View File

@ -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:

91
package.bluej Normal file
View File

@ -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