/*
* Copyright 2007-2013
* Licensed under GNU Lesser General Public License
*
* This file is part of EpochX: genetic programming software for research
*
* EpochX is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EpochX 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with EpochX. If not, see <http://www.gnu.org/licenses/>.
*
* The latest version is available from: http://www.epochx.org
*/
package org.epochx.epox;
import java.util.*;
import org.apache.commons.lang.*;
import org.epochx.tools.DataTypeUtils;
/**
* A <code>Node</code> is a vertex in a tree structure which represents a program.
* A node can be thought of as an expression in a computer programming language.
* Evaluating a node will involve evaluating any children and optionally returning a
* value.
*
* Subclasses of <code>Node</code> should ensure they call the superclass
* constructor with all child nodes so information such as the arity of the
* node can be maintained. Concrete subclasses must also implement
* <code>evaluate()</code> to evaluate the expression represented by the tree.
* Nodes which support mixed type arguments, or terminal nodes with no arguments
* must also override the <code>getReturnType(Class<?>)</code> method to
* indicate their return type. The <code>clone</code> and <code>newInstance</code>
* methods are also heavily used, so implementations should ensure this classes
* implementations are sufficient or override as necessary.
*
* @since 2.0
*/
public abstract class Node implements Cloneable {
// TODO Consider renaming to EpoxNode
private Node[] children;
private Node parent;
/**
* Constructs a new <code>Node</code> with the given child nodes. The arity of
* the node will be the number of child nodes provided. The child nodes may
* be initially set here as <code>null</code> and replaced before evaluation.
* Terminal nodes are simply nodes with no children.
*
* @param children child nodes to this node
*/
public Node(Node ... children) {
setChildren(children);
}
/**
* Subclasses should implement this method to perform some operation with
* respect to its children and return a result. If there is no result
* (for example, because this node has a <code>Void</code> data-type), then
* <code>null</code> should be returned.
*
* @return the result of evaluating the node tree rooted at this node
*/
public abstract Object evaluate();
/**
* Returns a specific child by index
*
* @param index the index of the child to be returned, valid indexes run
* from <code>0</code> to <code>getArity()-1</code>
* @return the child node at the specified index
*/
public Node getChild(int index) {
return children[index];
}
/**
* Returns the parent of this node or <code>null</code> if it is the root node
*
* @return the node that this node is a child of
*/
public Node getParent() {
return parent;
}
/**
* Returns an array of this node's children. Modifying this array will not
* change the set of children, but modifying the nodes will alter the nodes
* of the tree.
*
* @return an array of this node's children
*/
public Node[] getChildren() {
return (Node[]) ArrayUtils.clone(children);
}
/**
* Sets the child nodes of this node. Modifications to this array after
* being set will not modify the set of child nodes. The number of children
* set here does not need to match the current arity.
*
* @param children the nodes to set as children in order
*/
public void setChildren(Node ... children) {
// Must be careful to maintain the integrity of parent
this.children = new Node[children.length];
int index = 0;
for (Node child: children) {
setChild(index++, child);
}
}
/**
* Returns the element at the specified position in the node tree, where
* this node is considered to be the root - that is the 0th node. The tree
* is traversed in pre-order (depth first).
*
* @param n the index of the node to be returned
* @return the node at the specified position in this node tree
* @throws IndexOutOfBoundsException if <code>n</code> is out of range
*/
public Node getNode(int n) {
if (n >= 0) {
return getNode(n, 0);
} else {
throw new IndexOutOfBoundsException("attempt to get node at negative index");
}
}
/*
* Recursive helper for the public getNode(int)
*/
private Node getNode(int n, int current) {
if (n == current) {
return this;
}
Node node = null;
for (Node child: children) {
int childLength = child.length();
// Only look at the subtree if it contains the right range of nodes.
if (n <= childLength + current) {
node = child.getNode(n, current + 1);
if (node != null) {
break;
}
}
current += childLength;
}
// If node is null now then the index did not exist within any children.
if (node == null) {
throw new IndexOutOfBoundsException("attempt to get node at index >= length");
}
return node;
}
/**
* Replaces the node at the specified position in this node tree, where
* this node is considered to be the root node - that is, the 0th node.
* The tree is traversed in pre-order (depth first). It is not possible to
* set the 0th node, since it does not make sense for an object to be able
* to replace itself.
*
* @param n the index of the node to replace
* @param newNode the node to be stored at the specified position
* @throws IndexOutOfBoundsException if <code>n</code> is out of range
*/
public Node setNode(int n, Node newNode) {
if (n > 0) {
return setNode(n, newNode, 0);
} else if (n == 0) {
throw new IndexOutOfBoundsException("attempt to set node at index 0, cannot replace self");
} else {
throw new IndexOutOfBoundsException("attempt to set node at negative index");
}
}
/*
* Recursive helper for the public setNode(int, Node)
*/
private Node setNode(int n, Node newNode, int current) {
int arity = getArity();
for (int i = 0; i < arity; i++) {
if (current + 1 == n) {
Node old = getChild(i);
setChild(i, newNode);
return old;
}
Node child = getChild(i);
int childLength = child.length();
// Only look at the subtree if it contains the right range of nodes
if (n <= childLength + current) {
return child.setNode(n, newNode, current + 1);
}
current += childLength;
}
throw new IndexOutOfBoundsException("attempt to set node at index >= length");
}
/**
* Returns the index of the nth non-terminal node, where this node is
* considered to be the root - that is the 0th node. The tree's nodes are
* counted in pre-order (depth first) to locate the nth function, and return
* its index within all nodes. Will throw an exception if the index is out
* of bounds, which will be the case for all indexes when called on a
* terminal node.
*
* @param n the non-terminal to find the index of
* @return the index of the nth non-terminal node
* @throws IndexOutOfBoundsException if <code>n</code> is out of range
*/
public int nthNonTerminalIndex(int n) {
int index = nthNonTerminalIndex(n, 0, 0, this);
if (index < 0) {
throw new IndexOutOfBoundsException("attempt to get function node index at index out of range");
}
return index;
}
/*
* Recursive helper function for nthNonTerminalIndex
*/
private int nthNonTerminalIndex(int n, int functionCount, int nodeCount, Node current) {
if (current.isNonTerminal() && (n == functionCount)) {
return nodeCount;
}
int result = -1;
for (Node child: current.children) {
int noNodes = child.length();
int noFunctions = child.countNonTerminals();
// Only look at the subtree if it contains the right range of nodes
if (n <= noFunctions + functionCount) {
int childResult = nthNonTerminalIndex(n, functionCount + 1, nodeCount + 1, child);
if (childResult != -1) {
return childResult;
}
}
// Skip the correct number of nodes from the subtree
functionCount += noFunctions;
nodeCount += noNodes;
}
return result;
}
/**
* Returns the index of the nth terminal node, where this node is considered
* to be the root - that is the 0th node. The tree's nodes are counted in
* pre-order (depth first) to locate the nth terminal, and return its index
* within all nodes.
*
* @param n the terminal to find the index of
* @return the index of the nth terminal node
* @throws IllegalArgumentException if <code>n</code> is out of bounds
*/
public int nthTerminalIndex(int n) {
int index = nthTerminalIndex(n, 0, 0, this);
if (index < 0) {
throw new IndexOutOfBoundsException("attempt to get terminal node index at index out of range");
}
return index;
}
/*
* Recursive helper function for nthTerminalIndex
*/
private int nthTerminalIndex(int n, int terminalCount, int nodeCount, Node current) {
if (current.getArity() == 0) {
if (n == terminalCount++) {
return nodeCount;
}
}
int result = -1;
for (Node child: children) {
int noNodes = child.length();
int noTerminals = child.countTerminals();
// Only look at the subtree if it contains the right range of nodes
if (n <= noTerminals + terminalCount) {
int childResult = nthTerminalIndex(n, terminalCount, nodeCount + 1, child);
if (childResult != -1) {
return childResult;
}
}
// Skip the correct number of nodes from the subtree
terminalCount += noTerminals;
nodeCount += noNodes;
}
return result;
}
/**
* Retrieves all the nodes in the node tree at a specified depth from this
* current node. This node is considered to be at depth zero.
*
* @param depth the specified depth of the nodes to return
* @return a <code>List</code> of all the nodes at the specified depth
*/
public List<Node> nodesAtDepth(int depth) {
List<Node> nodes = new ArrayList<Node>((depth + 1) * 3);
if (depth >= 0) {
nodesAtDepth(nodes, depth, 0);
} else {
throw new IndexOutOfBoundsException("attempt to get nodes at negative depth");
}
if (nodes.isEmpty()) {
throw new IndexOutOfBoundsException("attempt to get nodes at depth greater than maximum depth");
}
return nodes;
}
/*
* A helper function for nodesAtDepth(int), to recurse down the node
* tree and populate the nodes array when at the correct depth.
*/
private void nodesAtDepth(List<Node> nodes, int d, int current) {
if (d == current) {
nodes.add(this);
} else {
for (Node child: children) {
// Get the nodes at the right depth down each branch
child.nodesAtDepth(nodes, d, current + 1);
}
}
}
/**
* Replaces the child node at the specified index with the given node
*
* @param index the index of the child to replace, from <code>0</code> to
* <code>getArity()-1</code>
* @param child the child node to be stored at the specified position
*/
public void setChild(int index, Node child) {
children[index] = child;
if (child != null) {
child.parent = this;
}
}
/**
* Returns the number of immediate children this <code>Node</code> has. This
* is effectively the number of inputs the node has. A node with arity
* zero, is considered to be a terminal node.
*
* @return the number of children required by this node
*/
public int getArity() {
return children.length;
}
/**
* Returns a count of the terminal nodes in this node tree
*
* @return the number of terminal nodes in this node tree
*/
public int countTerminals() {
if (isTerminal()) {
return 1;
} else {
int result = 0;
for (int i = 0; i < getArity(); i++) {
result += getChild(i).countTerminals();
}
return result;
}
}
/**
* Returns a count of the unique terminal nodes in the node tree below this
* node
*
* @return the number of unique terminal nodes in this node tree
*/
public int countDistinctTerminals() {
List<Node> terminals = listTerminals();
// Remove duplicates.
Set<Node> terminalHash = new HashSet<Node>(terminals);
return terminalHash.size();
}
/**
* Returns a list of all the terminal nodes in this node tree
*
* @return a <code>List</code> of all the terminal nodes in this node tree
*/
public List<Node> listTerminals() {
List<Node> terminals = new ArrayList<Node>();
int arity = getArity();
if (isTerminal()) {
terminals.add(this);
} else {
for (int i = 0; i < arity; i++) {
terminals.addAll(getChild(i).listTerminals());
}
}
return terminals;
}
/**
* Returns a count of the non-terminal nodes in this node tree
*
* @return the number of non-terminal nodes in this node tree
*/
public int countNonTerminals() {
if (isTerminal()) {
return 0;
} else {
int result = 1;
for (int i = 0; i < getArity(); i++) {
result += getChild(i).countNonTerminals();
}
return result;
}
}
/**
* Returns a count of the unique non-terminal nodes in this node tree
*
* @return the number of unique non-terminal nodes in this node tree
*/
public int countDistinctNonTerminals() {
List<Node> nonTerminals = listNonTerminals();
// Remove duplicates. Cannot use equals because that compares children
List<String> identifiers = new ArrayList<String>();
for (Node f: nonTerminals) {
String name = f.getIdentifier();
if (!identifiers.contains(name)) {
identifiers.add(name);
}
}
return identifiers.size();
}
/**
* Returns a list of all the non-terminal nodes in this node tree
*
* @return a <code>List</code> of all the non-terminal nodes in this node tree
*/
public List<Node> listNonTerminals() {
List<Node> nonTerminals = new ArrayList<Node>();
if (isNonTerminal()) {
// Add this node as a function and search its child nodes.
nonTerminals.add(this);
for (int i = 0; i < getArity(); i++) {
nonTerminals.addAll(getChild(i).listNonTerminals());
}
}
return nonTerminals;
}
/**
* Returns the depth of deepest node in the node tree, given that this node
* is at depth zero
*
* @return the depth of the deepest node in the node tree
*/
public int depth() {
return depth(this, 0, 0);
}
/*
* A private helper function for depth() which recurses down the node
* tree to determine the deepest node's depth
*/
private int depth(Node rootNode, int currentDepth, int depth) {
if (currentDepth > depth) {
depth = currentDepth;
}
int arity = rootNode.getArity();
if (arity > 0) {
for (int i = 0; i < arity; i++) {
Node childNode = rootNode.getChild(i);
depth = depth(childNode, (currentDepth + 1), depth);
}
}
return depth;
}
/**
* Returns the number of nodes in the node tree
*
* @return the number of nodes in the node tree
*/
public int length() {
return length(this, 0);
}
/*
* A private recursive helper function for length() which traverses the
* the node tree counting the number of nodes
*/
private int length(Node rootNode, int length) {
length++;
int arity = rootNode.getArity();
if (arity > 0) {
for (int i = 0; i < arity; i++) {
Node childNode = rootNode.getChild(i);
length = length(childNode, length);
}
}
return length;
}
/**
* Should be implemented to return an indentifier for this node. For
* functions, where this is effectively the function name, this would
* normally be unique within the given problem.
*
* @return a <code>String</code> identifier for this node.
*/
public abstract String getIdentifier();
/**
* Returns the data-type of this node based on the child nodes that are
* currently set. If any of this node's child nodes are currently
* <code>null</code>, or their data-types are invalid, then the return type will
* also be <code>null</code>.
*
* @return the return type of this node or <code>null</code> if any of its
* children remain unset or are of an invalid data-type
*/
public final Class<?> dataType() {
Class<?>[] argTypes = new Class<?>[getArity()];
for (int i = 0; i < getArity(); i++) {
Node child = getChild(i);
if (child != null) {
argTypes[i] = child.dataType();
} else {
return null;
}
}
return dataType(argTypes);
}
/**
* Returns this node's return type given the provided input data-types.
* The default implementation for a non-terminal is that the node will
* support the closure requirement - the return type will be the widest of
* the input types, or <code>null</code> if they are not compatible. The default
* return value for a terminal is <code>Void</code>. Mixed type non-terminal
* nodes and most terminal nodes should override this method. If the input
* types are invalid then <code>null</code> should be returned.
*
* @param inputTypes the set of input data-types for which to get the return
* type.
* @return the return type of this node given the provided input types, or
* null if the set of input types is invalid.
*/
public Class<?> dataType(Class<?> ... inputTypes) {
if (isTerminal()) {
return Void.class;
} else {
// Either the widest type or null if not valid
return DataTypeUtils.getSuper(inputTypes);
}
}
/**
* Returns <code>true</code> if this node has an arity of greater than
* <code>0</code>.
*
* @return <code>true</code> if this node is a non-terminal, and <code>false</code>
* otherwise.
*/
public boolean isNonTerminal() {
return (getArity() > 0);
}
/**
* Returns <code>true</code> if this node has an arity of <code>0</code>
*
* @return <code>true</code> if this node is a terminal, and <code>false</code>
* otherwise
*/
public boolean isTerminal() {
return (getArity() == 0);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int result = getIdentifier().hashCode();
for (final Node child: children) {
if (child != null) {
result = 37 * result + child.hashCode();
}
}
return result;
}
/**
* Creates a deep copy of this node and its node tree. Each child node will
* be cloned. Use this method for copying a whole node tree. Some
* implementations of this class may need to override this method.
*
* @return a copy of this <code>Node</code> and its children
*/
@Override
public Node clone() {
try {
Node clone = (Node) super.clone();
clone.children = children.clone();
for (int i = 0; i < children.length; i++) {
clone.children[i] = children[i]; // TODO Don't think we need
// this line.
if (clone.children[i] != null) {
clone.children[i] = clone.children[i].clone();
}
}
return clone;
} catch (final CloneNotSupportedException e) {
assert false;
// This shouldn't ever happen - if it does then everythings going to
// blow up anyway.
}
return null;
}
/**
* Constructs a new instance of this node-type. Rather than copying the node
* tree, this node is copied without children. In the case of many terminal
* nodes the bahaviour of this method will be the same as the clone method.
* By default this method will simply return a new instance of the same type
* in the same manner as clone, but with all children removed. Note that
* there is no requirement for an implementation to return a different
* instance.
*
* @return a copy of this <code>Node</code> with all children removed
*/
public Node newInstance() {
try {
Node n = (Node) super.clone();
n.children = new Node[children.length];
return n;
} catch (final CloneNotSupportedException e) {
assert false;
}
return null;
}
/**
* Compares an this node to another object for equality. Two nodes may be
* considered equal if they have equal arity, equal identifiers, and their
* children are also equal (and in the same order). Some nodes may wish to
* enforce a stricter contract.
*
* @param obj {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
boolean equal = true;
if (obj instanceof Node) {
Node n = (Node) obj;
if (n.getArity() != getArity()) {
equal = false;
} else if (!getIdentifier().equals(n.getIdentifier())) {
equal = false;
} else {
for (int i = 0; (i < n.getArity()) && equal; i++) {
Node thatChild = n.getChild(i);
Node thisChild = getChild(i);
equal = ObjectUtils.equals(thisChild, thatChild);
}
}
} else {
equal = false;
}
return equal;
}
/**
* Returns a string representation of this node. The default implementation
* is output of the form:
*
* <pre>
* identifier(children)
* </pre>
*
* where <code>identifier</code> is the node's identifier as returned by
* <code>getIdentifier</code>, and <code>children</code> is a space separated list
* of child nodes, according to their <code>toString</code> representation.
*
* @return a string representation of this node and its children
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getIdentifier());
builder.append('(');
for (int i = 0, n = children.length; i < n; i++) {
Node c = children[i];
if (i != 0) {
builder.append(' ');
}
if (c == null) {
builder.append('X');
} else {
builder.append(c.toString());
}
}
builder.append(')');
return builder.toString();
}
}