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 }