View Javadoc
1   package net.bmahe.genetics4j.core.chromosomes;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.Comparator;
6   import java.util.List;
7   import java.util.Objects;
8   import java.util.stream.Collectors;
9   
10  import org.apache.commons.lang3.Validate;
11  
12  /**
13   * Represents a node in a tree structure used for genetic programming and tree-based chromosomes.
14   * 
15   * <p>TreeNode provides a flexible, generic tree data structure that forms the foundation for tree-based genetic
16   * programming operations. Each node contains data of a specified type and can have zero or more child nodes, enabling
17   * the representation of complex hierarchical structures such as mathematical expressions, decision trees, or program
18   * syntax trees.
19   * 
20   * <p>Key features include:
21   * <ul>
22   * <li><strong>Generic data storage</strong>: Can hold any type of data (functions, terminals, values)</li>
23   * <li><strong>Dynamic structure</strong>: Supports adding, removing, and modifying children</li>
24   * <li><strong>Tree metrics</strong>: Provides size and depth calculations for analysis</li>
25   * <li><strong>Hierarchical operations</strong>: Enables tree traversal and manipulation</li>
26   * </ul>
27   * 
28   * <p>Common uses in genetic programming:
29   * <ul>
30   * <li><strong>Expression trees</strong>: Mathematical or logical expressions with operators and operands</li>
31   * <li><strong>Program trees</strong>: Structured representation of executable code or algorithms</li>
32   * <li><strong>Decision trees</strong>: Conditional logic structures for classification or control</li>
33   * <li><strong>Grammar trees</strong>: Parse trees representing valid grammatical constructs</li>
34   * </ul>
35   * 
36   * <p>Tree structure characteristics:
37   * <ul>
38   * <li><strong>Mutable structure</strong>: Children can be added, removed, or replaced during evolution</li>
39   * <li><strong>Type safety</strong>: Generic parameterization ensures consistent data types</li>
40   * <li><strong>Recursive operations</strong>: Size and depth calculations traverse the entire subtree</li>
41   * <li><strong>Equality semantics</strong>: Two trees are equal if their structure and data match</li>
42   * </ul>
43   * 
44   * <p>Example usage in genetic programming:
45   * 
46   * <pre>{@code
47   * // Creating a simple mathematical expression: (x + 2) * 3
48   * TreeNode<String> multiply = new TreeNode<>("*");
49   * TreeNode<String> add = new TreeNode<>("+");
50   * TreeNode<String> x = new TreeNode<>("x");
51   * TreeNode<String> two = new TreeNode<>("2");
52   * TreeNode<String> three = new TreeNode<>("3");
53   * 
54   * add.addChild(x);
55   * add.addChild(two);
56   * multiply.addChild(add);
57   * multiply.addChild(three);
58   * 
59   * // Tree metrics
60   * int treeSize = multiply.getSize(); // Total nodes in tree
61   * int treeDepth = multiply.getDepth(); // Maximum depth from root
62   * 
63   * // Factory method for creating nodes with children
64   * TreeNode<String> expression = TreeNode
65   * 		.of("*", List.of(TreeNode.of("+", List.of(new TreeNode<>("x"), new TreeNode<>("2"))), new TreeNode<>("3")));
66   * }</pre>
67   * 
68   * <p>Integration with genetic operations:
69   * <ul>
70   * <li><strong>Crossover</strong>: Subtree exchange between parent trees</li>
71   * <li><strong>Mutation</strong>: Replacement or modification of individual nodes or subtrees</li>
72   * <li><strong>Size constraints</strong>: Enforcement of maximum tree size or depth limits</li>
73   * <li><strong>Evaluation</strong>: Tree traversal for fitness computation</li>
74   * </ul>
75   * 
76   * <p>Performance considerations:
77   * <ul>
78   * <li><strong>Memory usage</strong>: Each node maintains a list of children references</li>
79   * <li><strong>Tree traversal</strong>: Size and depth calculations have O(n) complexity</li>
80   * <li><strong>Structural sharing</strong>: Nodes can be shared between trees with care</li>
81   * <li><strong>Deep trees</strong>: Very deep trees may cause stack overflow in recursive operations</li>
82   * </ul>
83   * 
84   * @param <T> the type of data stored in each node
85   * @see TreeChromosome
86   * @see TreeChromosome
87   */
88  public class TreeNode<T> {
89  
90  	private final T data;
91  
92  	private final ArrayList<TreeNode<T>> children;
93  
94  	/**
95  	 * Constructs a new tree node with the specified data and no children.
96  	 * 
97  	 * <p>Creates a leaf node that can later have children added to it. The data provided becomes the payload for this
98  	 * node and cannot be changed after construction.
99  	 * 
100 	 * @param _data the data to store in this node
101 	 * @throws IllegalArgumentException if data is null
102 	 */
103 	public TreeNode(final T _data) {
104 		Objects.requireNonNull(_data);
105 
106 		this.data = _data;
107 		this.children = new ArrayList<>();
108 	}
109 
110 	/**
111 	 * Returns the data stored in this node.
112 	 * 
113 	 * @return the data payload of this node
114 	 */
115 	public T getData() {
116 		return data;
117 	}
118 
119 	/**
120 	 * Returns the list of direct children of this node.
121 	 * 
122 	 * <p>The returned list is the actual internal list used by this node. Modifications to the returned list will affect
123 	 * this node's structure.
124 	 * 
125 	 * @return the mutable list of child nodes
126 	 */
127 	public List<TreeNode<T>> getChildren() {
128 		return children;
129 	}
130 
131 	/**
132 	 * Returns the child node at the specified index.
133 	 * 
134 	 * @param childIndex the index of the child to retrieve (0-based)
135 	 * @return the child node at the specified index
136 	 * @throws IllegalArgumentException  if childIndex is negative
137 	 * @throws IndexOutOfBoundsException if childIndex is >= number of children
138 	 */
139 	public TreeNode<T> getChild(final int childIndex) {
140 		Validate.isTrue(childIndex >= 0);
141 
142 		return children.get(childIndex);
143 	}
144 
145 	/**
146 	 * Replaces the child node at the specified index with a new node.
147 	 * 
148 	 * <p>This operation modifies the tree structure by replacing an existing child with a new subtree rooted at the
149 	 * provided node.
150 	 * 
151 	 * @param childIndex the index of the child to replace (0-based)
152 	 * @param childData  the new child node to set at the specified index
153 	 * @throws IllegalArgumentException  if childIndex is negative
154 	 * @throws IndexOutOfBoundsException if childIndex is >= number of children
155 	 */
156 	public void setChild(final int childIndex, final TreeNode<T> childData) {
157 		Validate.isTrue(childIndex >= 0);
158 
159 		children.set(childIndex, childData);
160 	}
161 
162 	/**
163 	 * Adds a new child node to this node.
164 	 * 
165 	 * <p>The new child is appended to the end of the children list. This operation increases the arity of this node by
166 	 * one.
167 	 * 
168 	 * @param childData the child node to add
169 	 * @throws IllegalArgumentException if childData is null
170 	 */
171 	public void addChild(final TreeNode<T> childData) {
172 		Objects.requireNonNull(childData);
173 
174 		children.add(childData);
175 	}
176 
177 	/**
178 	 * Adds multiple child nodes to this node.
179 	 * 
180 	 * <p>All nodes in the provided collection are appended to the children list in the order they appear in the
181 	 * collection.
182 	 * 
183 	 * @param childrenNodes the collection of child nodes to add
184 	 * @throws IllegalArgumentException if childrenNodes is null or empty
185 	 */
186 	public void addChildren(final Collection<TreeNode<T>> childrenNodes) {
187 		Objects.requireNonNull(childrenNodes);
188 		Validate.isTrue(childrenNodes.isEmpty() == false);
189 
190 		children.addAll(childrenNodes);
191 	}
192 
193 	/**
194 	 * Returns the total number of nodes in the subtree rooted at this node.
195 	 * 
196 	 * <p>This method recursively counts all nodes in the subtree, including this node and all its descendants. The size
197 	 * is useful for analyzing tree complexity and implementing size-based genetic operations.
198 	 * 
199 	 * @return the total number of nodes in this subtree (always >= 1)
200 	 */
201 	public int getSize() {
202 		return 1 + children.stream().map(TreeNode::getSize).collect(Collectors.summingInt(x -> x));
203 	}
204 
205 	/**
206 	 * Returns the maximum depth of the subtree rooted at this node.
207 	 * 
208 	 * <p>The depth is defined as the length of the longest path from this node to any leaf node in the subtree. A leaf
209 	 * node has depth 1.
210 	 * 
211 	 * @return the maximum depth of this subtree (always >= 1)
212 	 */
213 	public int getDepth() {
214 		return 1 + children.stream().map(TreeNode::getDepth).max(Comparator.naturalOrder()).orElse(0);
215 	}
216 
217 	@Override
218 	public int hashCode() {
219 		final int prime = 31;
220 		int result = 1;
221 		result = prime * result + (children == null ? 0 : children.hashCode());
222 		return prime * result + (data == null ? 0 : data.hashCode());
223 	}
224 
225 	@Override
226 	public boolean equals(Object obj) {
227 		if (this == obj) {
228 			return true;
229 		}
230 		if (obj == null) {
231 			return false;
232 		}
233 		if (getClass() != obj.getClass()) {
234 			return false;
235 		}
236 		TreeNode other = (TreeNode) obj;
237 		if (data == null) {
238 			if (other.data != null) {
239 				return false;
240 			}
241 		} else if (!data.equals(other.data)) {
242 			return false;
243 		}
244 		if (children == null) {
245 			if (other.children != null) {
246 				return false;
247 			}
248 		} else if (!children.equals(other.children)) {
249 			return false;
250 		}
251 
252 		return true;
253 	}
254 
255 	@Override
256 	public String toString() {
257 		return "TreeNode [data=" + data + ", children=" + children + "]";
258 	}
259 
260 	/**
261 	 * Creates a new tree node with the specified data and children.
262 	 * 
263 	 * <p>This factory method provides a convenient way to construct trees with a specific structure in a single
264 	 * operation. All provided children are added to the new node.
265 	 * 
266 	 * @param <U>      the type of data stored in the nodes
267 	 * @param data     the data to store in the root node
268 	 * @param children the collection of child nodes to add
269 	 * @return a new tree node with the specified data and children
270 	 * @throws IllegalArgumentException if data is null, children is null, or children is empty
271 	 */
272 	public static <U> TreeNode<U> of(final U data, final Collection<TreeNode<U>> children) {
273 		Objects.requireNonNull(data);
274 		Objects.requireNonNull(children);
275 		Validate.isTrue(children.isEmpty() == false);
276 
277 		final TreeNode<U> rootNode = new TreeNode<>(data);
278 		for (TreeNode<U> childNode : children) {
279 			rootNode.addChild(childNode);
280 		}
281 
282 		return rootNode;
283 	}
284 }