/***********************************************************************
This file is part of KEEL-software, the Data Mining tool for regression,
classification, clustering, pattern mining and so on.
Copyright (C) 2004-2010
F. Herrera (herrera@decsai.ugr.es)
L. S�nchez (luciano@uniovi.es)
J. Alcal�-Fdez (jalcala@decsai.ugr.es)
S. Garc�a (sglopez@ujaen.es)
A. Fern�ndez (alberto.fernandez@ujaen.es)
J. Luengo (julianlm@decsai.ugr.es)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/
**********************************************************************/
package keel.Algorithms.Decision_Trees.PUBLIC;
import java.util.ArrayList;
/**
*
* File: TreeNode.java
*
* Data structure that is used in the construction of the decision tree.
* It stores the information about the relationship between nodes in a tree, and
* the type of node that it is. This class has a identifier that relates the
* instances of this class with the instances of the Node class.
*
* @author Written by Victoria Lopez Morales (University of Granada) 29/04/2009
* @version 1.0
* @since JDK1.5
*/
public class TreeNode {
/**
* Identifier of the node
*/
int identifier;
/**
* Left descendant of this node, if it is not a leaf node
*/
TreeNode left;
/**
* Right descendant of this node, if it is not a leaf node
*/
TreeNode right;
/**
* Whether this node is a leaf node or not. A node that isn't a leaf node can be
* changed to a leaf node anytime, but a leaf node can't become a no leaf node
*/
boolean isLeaf;
/**
* The class asigned to this node if it is a leaf node, otherwise this class has
* no meaning, but it is usually -1
*/
int outputClass;
/**
* Relationship between this node and its descendants
*/
Split condition;
/**
* Creates a node with empty values that we can identify
*/
TreeNode () {
identifier = 0;
left = null;
right = null;
isLeaf = false;
outputClass = -1;
condition = null;
}
/**
* Creates a node with the identifier, the descendants and its condition as a leaf node. It also
* includes the output class selected for it and the relationship between the nodes
*
* @param id Number identifying the node that is being created
* @param newleft Left descendant of the node that is being created
* @param newright Right descendant of the node that is being created
* @param leaf Whether the new node is a leaf node or not
* @param oclass Output class for the node that is being created
* @param cond Way to split this node into its descendants
*/
TreeNode (int id, TreeNode newleft, TreeNode newright, boolean leaf, int oclass, Split cond) {
identifier = id;
left = newleft;
right = newright;
isLeaf = leaf;
outputClass = oclass;
condition = cond;
}
/**
* Creates a node with the identifier, its condition as a leaf node and the output class selected
* for it; the rest of the values are initialized with empty values that we can identify. It is used
* when we create a leaf node
*
* @param id Number identifying the node that is being created
* @param leaf Whether the new node is a leaf node or not
* @param oclass Output class for the node that is being created
*/
TreeNode (int id, boolean leaf, int oclass) {
identifier = id;
left = null;
right = null;
isLeaf = leaf;
outputClass = oclass;
condition = null;
}
/**
* Creates a node from another existing node
*
* @param tree Original node from which we are going to create a copy
*/
TreeNode (TreeNode tree) {
this.identifier = tree.identifier;
this.left = new TreeNode(tree.left);
this.right = new TreeNode(tree.right);
this.isLeaf = tree.isLeaf;
this.outputClass = tree.outputClass;
this.condition = new Split (tree.condition);
}
/**
* Checks if a node is the same node as another object
*
* @param obj Object that is checked to see if it is the same node
* @return true if the nodes are the same, false otherwise
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals (Object obj) {
// First we check if the reference is the same
if (this == obj)
return true;
// Then we check if the object exists and is from the class TreeNode
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be TreeNode at this point
TreeNode test = (TreeNode)obj;
// We check the class attributes of the TreeNode class
return ((identifier == test.identifier) && (isLeaf == test.isLeaf) && (outputClass == test.outputClass) && (left == test.left || (left != null && left.equals(test.left))) && (right == test.right || (right != null && right.equals(test.right))) && (condition == test.condition || (condition != null && condition.equals(test.condition))));
}
/**
* Hash-code function for the class that is used when object is inserted in a structure like a hashtable
*
* @return the hash code obtained
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
int hash = 7;
hash = 31 * hash + identifier;
hash = 31 * hash + (null == left ? 0 : left.hashCode());
hash = 31 * hash + (null == right ? 0 : right.hashCode());
hash = 31 * hash + (isLeaf ? 1 : 0);
hash = 31 * hash + outputClass;
hash = 31 * hash + (null == condition ? 0 : condition.hashCode());
return hash;
}
/**
* Overriden function that converts the class to a string
*
* @return the string representation of the class
* @see java.lang.Object#toString()
*/
public String toString() {
try {
StringBuffer text = new StringBuffer();
if (!isLeaf) {
printTree(0, text);
}
return text.toString();
} catch (Exception e) {
return "Can not print the tree";
}
}
/**
* Prints the tree in a StringBuffer with a depth given according to the relationship
* of the nodes in the whole tree
*
* @param depth Position in the tree of the node that is reflected in the string in a
* major number of space in it
* @param text Output where the tree is exposed
* @throws Exception If the tree cannot be printed.
*/
private void printTree (int depth, StringBuffer text) throws Exception {
String aux = "";
for (int k = 0; k < depth; k++) {
aux += "\t";
}
text.append(aux);
if (isLeaf) {
text.append(outputClass + " \n");
}
else {
text.append("if (" + condition + ") then {\n");
left.printTree(depth + 1, text);
text.append(aux + "else { \n");
right.printTree(depth + 1, text);
}
text.append(aux + "}\n");
}
/**
* Prints the tree in a String with all the information that makes it human readable
*
* @param atts Attributes used in the dataset for building the tree that are needed to change
* the numberical information stored in the tree to words information
* @param outputAttribute Attribute used for the class in the dataset for building the tree
* that is needed in a similar way to the attributes
* @return a String with the tree in it
*/
public String printTree (ArrayList <myAttribute> atts, myAttribute outputAttribute) {
try {
StringBuffer text = new StringBuffer();
if (!isLeaf) {
printTree(0, text, atts, outputAttribute);
}
return text.toString();
} catch (Exception e) {
return "Can not print the tree.";
}
}
/**
* Prints the tree in a StringBuffer with a depth given according to the relationship
* of the nodes in the whole tree with all the information that makes it human readable
*
* @param depth Position in the tree of the node that is reflected in the string in a
* major number of space in it
* @param text Output where the tree is exposed
* @param atts Attributes used in the dataset for building the tree that are needed to change
* the numberical information stored in the tree to words information
* @param outputAttribute Attribute used for the class in the dataset for building the tree
* that is needed in a similar way to the attributes
* @throws Exception If the tree cannot be printed.
*/
private void printTree (int depth, StringBuffer text, ArrayList <myAttribute> atts, myAttribute outputAttribute) throws Exception {
String aux = "";
String aux2 = "";
for (int k = 0; k < depth; k++) {
aux += "\t";
}
for (int k = 1; k < depth; k++) {
aux2 += "\t";
}
text.append(aux);
if (isLeaf) {
if (outputAttribute.isNominal()) {
text.append(outputAttribute.getValues().get(outputClass) + " \n");
}
else {
text.append(outputClass + " \n");
}
}
else {
if (atts.get(condition.getAttribute()).isNominal()) {
text.append("if (" + atts.get(condition.getAttribute()).getName() + " in " + atts.get(condition.getAttribute()).getValues().get((int)condition.getValue()) + ") then {\n");
}
else {
text.append("if (" + atts.get(condition.getAttribute()).getName() + " < " + condition.getValue() + ") then {\n");
}
left.printTree(depth + 1, text, atts, outputAttribute);
text.append(aux + "else { \n");
right.printTree(depth + 1, text, atts, outputAttribute);
}
text.append(aux2 + "}\n");
}
/**
* Gets the identifier of the node
*
* @return the identifier of the node
*/
public int getIdentifier() {
return identifier;
}
/**
* Replaces the identifier of the node with another new node
*
* @param identifier New identifier for the node
*/
public void setIdentifier(int identifier) {
this.identifier = identifier;
}
/**
* Gets the left descendant of the node, if it is not a leaf node
*
* @return the left descendant of the node
*/
public TreeNode getLeft() {
return left;
}
/**
* Replaces the left descendant of the node with another new left descendant
*
* @param left New node that is going to be kept as left descendant of this node
*/
public void setLeft(TreeNode left) {
this.left = left;
}
/**
* Gets the right descendant of the node, if it is not a leaf node
*
* @return the left descendant of the node
*/
public TreeNode getRight() {
return right;
}
/**
* Replaces the right descendant of the node with another new right descendant
*
* @param right New node that is going to be kept as right descendant of this node
*/
public void setRight(TreeNode right) {
this.right = right;
}
/**
* Answers if the node is a leaf node or not
*
* @return true if the node is a leaf node, false otherwise
*/
public boolean isLeaf() {
return isLeaf;
}
/**
* Changes the logical attribute stating if a node is leaf or not
*
* @param isLeaf Logical value stating if a node is leaf or not
*/
public void setLeaf(boolean isLeaf) {
this.isLeaf = isLeaf;
}
/**
* Gets the output class stored for the node. It should be considered only when the node is a leaf node
*
* @return the output class of the node
*/
public int getOutputClass() {
return outputClass;
}
/**
* Replaces the output class of the node with another new output class
*
* @param outputClass New output class for the node
*/
public void setOutputClass(int outputClass) {
this.outputClass = outputClass;
}
/**
* Gets the relationship between this node and its descendats, that's the way to split this node in two
*
* @return the split for this node
*/
public Split getCondition() {
return condition;
}
/**
* Replaces the relationship between this node and its descendants, this means, changing the condition how
* the two descendants are created. When using this function is highly recommended using the setLeft() and
* setRight() functions too
*
* @param condition New relationship between this node and its descendants
*/
public void setCondition(Split condition) {
this.condition = condition;
}
/**
* Searches a descendant node from this node with a specific id
*
* @param id Identifier of the node we are searching for
* @return the TreeNode with the specified id
*/
public TreeNode getNode (int id) {
TreeNode aux = null;
if (this == null) {
return null;
}
if (identifier == id) {
return this;
}
if (left != null)
aux = left.getNode(id);
if ((aux == null) && (right != null)) {
aux = right.getNode(id);
}
return aux;
}
/**
* Gets the number of internal nodes from this node and its descendants. If this is a leaf node the number
* of internal nodes is 0.
*
* @return the number of internal nodes from this node and its descendants
*/
public int getNumNodes () {
int nodes = 0;
if (isLeaf) {
nodes = 0;
}
else {
nodes++;
if (left != null)
nodes += left.getNumNodes();
if (right != null)
nodes += right.getNumNodes();
}
return nodes;
}
/**
* Gets the number of leaf nodes from this node and its descendants
*
* @return the number of leaf nodes from this node and its descendants
*/
public int getLeafs () {
int leafs = 0;
if (isLeaf) {
leafs = 1;
}
else {
if (left != null)
leafs += left.getLeafs();
if (right != null)
leafs += right.getLeafs();
}
return leafs;
}
/**
* Classifies a given item with the information stored in the node and its descendants
*
* @param ind Data attribute values for the item we are classifying
* @param atts Attributes in the data set that are used for building the tree and describing the
* instance given
* @return the class asigned to the item given
*/
public int evaluate (double [] ind, ArrayList <myAttribute> atts) {
if (isLeaf) {
return outputClass;
}
else {
if (condition != null) {
if (atts.get(condition.getAttribute()).isNominal()) {
if (ind[condition.getAttribute()] == condition.getValue()) {
return left.evaluate(ind, atts);
}
else {
return right.evaluate(ind, atts);
}
}
else {
if (ind[condition.getAttribute()] < condition.getValue()) {
return left.evaluate(ind, atts);
}
else {
return right.evaluate(ind, atts);
}
}
}
else {
System.err.println("Tree not fully built");
System.exit(-1);
return -1;
}
}
}
/**
* Removes the descendants from a identifier given of a node. The nodes whose descendants are being
* removed become leaf nodes
*
* @param original_identifier Identifier of the node that is becoming a leaf node and whose descendants
* are being removed
* @return ArrayList with all the identifiers of the nodes that are being removed
*/
public ArrayList <Integer> deleteDescendants (int original_identifier) {
ArrayList <Integer> nodes_deleted;
nodes_deleted = new ArrayList <Integer> ();
// Obtain descendants of descendants
if (left != null) {
nodes_deleted.addAll(left.deleteDescendants(original_identifier));
}
if (right != null) {
nodes_deleted.addAll(right.deleteDescendants(original_identifier));
}
left = null;
right = null;
condition = null;
if (identifier != original_identifier) {
nodes_deleted.add(identifier);
}
return nodes_deleted;
}
}