FlatTreeNode.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-7.1.0).
003  * Copyright (c) 2007-2022 Franz Wilhelmstötter
004  *
005  * Licensed under the Apache License, Version 2.0 (the "License");
006  * you may not use this file except in compliance with the License.
007  * You may obtain a copy of the License at
008  *
009  *      http://www.apache.org/licenses/LICENSE-2.0
010  *
011  * Unless required by applicable law or agreed to in writing, software
012  * distributed under the License is distributed on an "AS IS" BASIS,
013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  * See the License for the specific language governing permissions and
015  * limitations under the License.
016  *
017  * Author:
018  *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019  */
020 package io.jenetics.ext.util;
021 
022 import static java.util.Objects.requireNonNull;
023 import static io.jenetics.internal.util.SerialIO.readIntArray;
024 import static io.jenetics.internal.util.SerialIO.readObjectArray;
025 import static io.jenetics.internal.util.SerialIO.writeIntArray;
026 import static io.jenetics.internal.util.SerialIO.writeObjectArray;
027 
028 import java.io.IOException;
029 import java.io.InvalidObjectException;
030 import java.io.ObjectInput;
031 import java.io.ObjectInputStream;
032 import java.io.ObjectOutput;
033 import java.io.Serial;
034 import java.io.Serializable;
035 import java.lang.reflect.Array;
036 import java.util.Arrays;
037 import java.util.Iterator;
038 import java.util.Optional;
039 import java.util.function.BiFunction;
040 import java.util.function.Function;
041 import java.util.stream.IntStream;
042 import java.util.stream.Stream;
043 
044 import io.jenetics.util.ISeq;
045 
046 /**
047  * Default implementation of the {@link FlatTree} interface. Beside the
048  * flattened and dense layout it is also an <em>immutable</em> implementation of
049  * the {@link Tree} interface. It can only be created from an existing tree.
050  *
051  <pre>{@code
052  * final Tree<String, ?> immutable = FlatTreeNode.ofTree(TreeNode.parse(...));
053  * }</pre>
054  *
055  * @implNote
056  * This class is immutable and thread-safe.
057  *
058  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
059  @version 7.1
060  @since 3.9
061  */
062 public final class FlatTreeNode<V>
063     implements
064         FlatTree<V, FlatTreeNode<V>>,
065         Serializable
066 {
067 
068     /**
069      * The flattened tree nodes.
070      */
071     private record Nodes(Object[] values, int[] childOffsets, int[] childCounts) {
072         Nodes(final int size) {
073             this(new Object[size]new int[size]new int[size]);
074         }
075     }
076 
077     @Serial
078     private static final long serialVersionUID = 3L;
079 
080     private static final int NULL_INDEX = -1;
081 
082     private final Nodes _nodes;
083     private final int _index;
084 
085     private FlatTreeNode(final Nodes nodes, final int index) {
086         _nodes = requireNonNull(nodes);
087         _index = index;
088     }
089 
090     private FlatTreeNode(final Nodes nodes) {
091         this(nodes, 0);
092     }
093 
094     /**
095      * Returns the root of the tree that contains this node. The root is the
096      * ancestor with no parent. This implementation has a runtime complexity
097      * of O(1).
098      *
099      @return the root of the tree that contains this node
100      */
101     @Override
102     public FlatTreeNode<V> root() {
103         return nodeAt(0);
104     }
105 
106     @Override
107     public boolean isRoot() {
108         return _index == 0;
109     }
110 
111     private FlatTreeNode<V> nodeAt(final int index) {
112         return new FlatTreeNode<>(_nodes, index);
113     }
114 
115     @SuppressWarnings("unchecked")
116     @Override
117     public V value() {
118         return (V)_nodes.values[_index];
119     }
120 
121     @Override
122     public Optional<FlatTreeNode<V>> parent() {
123         int index = NULL_INDEX;
124         for (int i = _index; --i >= && index == NULL_INDEX;) {
125             if (isParent(i)) {
126                 index = i;
127             }
128         }
129 
130         return index != NULL_INDEX
131             ? Optional.of(nodeAt(index))
132             : Optional.empty();
133     }
134 
135     private boolean isParent(final int index) {
136         return _nodes.childCounts[index&&
137             _nodes.childOffsets[index<= _index &&
138             _nodes.childOffsets[index+ _nodes.childCounts[index> _index;
139     }
140 
141     @Override
142     public FlatTreeNode<V> childAt(final int index) {
143         if (index < || index >= childCount()) {
144             throw new IndexOutOfBoundsException(Integer.toString(index));
145         }
146 
147         return nodeAt(childOffset() + index);
148     }
149 
150     @Override
151     public int childCount() {
152         return _nodes.childCounts[_index];
153     }
154 
155     /**
156      * Return the index of the first child node in the underlying node array.
157      * {@code -1} is returned if {@code this} node is a leaf.
158      *
159      @return Return the index of the first child node in the underlying node
160      *         array, or {@code -1} if {@code this} node is a leaf
161      */
162     @Override
163     public int childOffset() {
164         return _nodes.childOffsets[_index];
165     }
166 
167     @Override
168     public ISeq<FlatTreeNode<V>> flattenedNodes() {
169         return stream().collect(ISeq.toISeq());
170     }
171 
172     @Override
173     public Iterator<FlatTreeNode<V>> breadthFirstIterator() {
174         return isRoot()
175             new IntFunctionIterator<>(this::nodeAt, _nodes.values.length)
176             : FlatTree.super.breadthFirstIterator();
177     }
178 
179     @Override
180     public Stream<FlatTreeNode<V>> breadthFirstStream() {
181         return isRoot()
182             ? IntStream.range(0, _nodes.values.length).mapToObj(this::nodeAt)
183             : FlatTree.super.breadthFirstStream();
184     }
185 
186     /**
187      * Return a sequence of all <em>mapped</em> nodes of the whole underlying
188      * tree. This is a convenient method for
189      <pre>{@code
190      * final ISeq<B> seq = stream()
191      *     .map(mapper)
192      *     .collect(ISeq.toISeq())
193      * }</pre>
194      *
195      @param mapper the mapper function
196      @param <B> the mapped type
197      @return a sequence of all <em>mapped</em> nodes
198      */
199     public <B> ISeq<B>
200     map(final Function<? super FlatTreeNode<V>, ? extends B> mapper) {
201         return stream()
202             .map(mapper)
203             .collect(ISeq.toISeq());
204     }
205 
206     @Override
207     public boolean identical(final Tree<?, ?> other) {
208         return other == this ||
209             other instanceof FlatTreeNode<?> node &&
210             node._index == _index &&
211             node._nodes == _nodes;
212     }
213 
214     @Override
215     public <U> U reduce(
216         final U[] neutral,
217         final BiFunction<? super V, ? super U[], ? extends U> reducer
218     ) {
219         requireNonNull(neutral);
220         requireNonNull(reducer);
221 
222         @SuppressWarnings("unchecked")
223         final class Reducing {
224             private U reduce(final int index) {
225                 return _nodes.childCounts[index== 0
226                     ? reducer.apply((V)_nodes.values[index], neutral)
227                     : reducer.apply((V)_nodes.values[index], children(index));
228             }
229             private U[] children(final int index) {
230                 final U[] values = (U[])Array.newInstance(
231                     neutral.getClass().getComponentType(),
232                     _nodes.childCounts[index]
233                 );
234                 for (int i = 0; i < _nodes.childCounts[index]; ++i) {
235                     values[i= reduce(_nodes.childOffsets[index+ i);
236                 }
237                 return values;
238             }
239         }
240 
241         return isEmpty() null new Reducing().reduce(_index);
242     }
243 
244     @Override
245     public int hashCode() {
246         return Tree.hashCode(this);
247     }
248 
249     @Override
250     public boolean equals(final Object obj) {
251         return obj == this ||
252             (obj instanceof FlatTreeNode<?> ftn && equals(ftn)) ||
253             (obj instanceof Tree<?, ?> tree && Tree.equals(tree, this));
254     }
255 
256     private boolean equals(final FlatTreeNode<?> tree) {
257         return tree._index == _index &&
258             Arrays.equals(tree._nodes.values, _nodes.values&&
259             Arrays.equals(tree._nodes.childCounts, _nodes.childCounts&&
260             Arrays.equals(tree._nodes.childOffsets, _nodes.childOffsets);
261     }
262 
263     @Override
264     public String toString() {
265         return toParenthesesString();
266     }
267 
268     @Override
269     public int size() {
270         return isRoot()
271             ? _nodes.values.length
272             : countChildren(_index1;
273     }
274 
275     private int countChildren(final int index) {
276         int count = _nodes.childCounts[index];
277         for (int i = 0; i < _nodes.childCounts[index]; ++i) {
278             count += countChildren(_nodes.childOffsets[index+ i);
279         }
280         return count;
281     }
282 
283     /* *************************************************************************
284      *  Static factories
285      * ************************************************************************/
286 
287     /**
288      * Create a new, immutable {@code FlatTreeNode} from the given {@code tree}.
289      *
290      @param tree the source tree
291      @param <V> the tree value types
292      @return a {@code FlatTreeNode} from the given {@code tree}
293      @throws NullPointerException if the given {@code tree} is {@code null}
294      */
295     public static <V> FlatTreeNode<V> ofTree(final Tree<? extends V, ?> tree) {
296         requireNonNull(tree);
297 
298         if (tree instanceof FlatTreeNode<?> ft && ft.isRoot()) {
299             @SuppressWarnings("unchecked")
300             final var result = (FlatTreeNode<V>)ft;
301             return result;
302         }
303 
304         final int size = tree.size();
305         assert size >= 1;
306 
307         final var nodes = new Nodes(size);
308 
309         int childOffset = 1;
310         int index = 0;
311 
312         for (var node : tree) {
313             nodes.values[index= node.value();
314             nodes.childCounts[index= node.childCount();
315             nodes.childOffsets[index= node.isLeaf() ? NULL_INDEX : childOffset;
316 
317             childOffset += node.childCount();
318             ++index;
319         }
320         assert index == size;
321 
322         return new FlatTreeNode<>(nodes);
323     }
324 
325     /**
326      * Parses a (parentheses) tree string, created with
327      {@link Tree#toParenthesesString()}. The tree string might look like this:
328      <pre>
329      *  mul(div(cos(1.0),cos(π)),sin(mul(1.0,z)))
330      </pre>
331      *
332      @see Tree#toParenthesesString(Function)
333      @see Tree#toParenthesesString()
334      @see TreeNode#parse(String)
335      *
336      @since 5.0
337      *
338      @param tree the parentheses tree string
339      @return the parsed tree
340      @throws NullPointerException if the given {@code tree} string is
341      *         {@code null}
342      @throws IllegalArgumentException if the given tree string could not be
343      *         parsed
344      */
345     public static FlatTreeNode<String> parse(final String tree) {
346         return ofTree(ParenthesesTreeParser.parse(tree, Function.identity()));
347     }
348 
349     /**
350      * Parses a (parentheses) tree string, created with
351      {@link Tree#toParenthesesString()}. The tree string might look like this
352      <pre>
353      *  0(1(4,5),2(6),3(7(10,11),8,9))
354      </pre>
355      * and can be parsed to an integer tree with the following code:
356      <pre>{@code
357      * final Tree<Integer, ?> tree = FlatTreeNode.parse(
358      *     "0(1(4,5),2(6),3(7(10,11),8,9))",
359      *     Integer::parseInt
360      * );
361      * }</pre>
362      *
363      @see Tree#toParenthesesString(Function)
364      @see Tree#toParenthesesString()
365      @see TreeNode#parse(String, Function)
366      *
367      @since 5.0
368      *
369      @param <B> the tree node value type
370      @param tree the parentheses tree string
371      @param mapper the mapper which converts the serialized string value to
372      *        the desired type
373      @return the parsed tree object
374      @throws NullPointerException if one of the arguments is {@code null}
375      @throws IllegalArgumentException if the given parentheses tree string
376      *         doesn't represent a valid tree
377      */
378     public static <B> FlatTreeNode<B> parse(
379         final String tree,
380         final Function<? super String, ? extends B> mapper
381     ) {
382         return ofTree(ParenthesesTreeParser.parse(tree, mapper));
383     }
384 
385 
386     /* *************************************************************************
387      *  Java object serialization
388      * ************************************************************************/
389 
390     @Serial
391     private Object writeReplace() {
392         return new SerialProxy(SerialProxy.FLAT_TREE_NODE, this);
393     }
394 
395     @Serial
396     private void readObject(final ObjectInputStream stream)
397         throws InvalidObjectException
398     {
399         throw new InvalidObjectException("Serialization proxy required.");
400     }
401 
402 
403     void write(final ObjectOutput outthrows IOException {
404         final FlatTreeNode<V> node = isRoot()
405             this
406             : FlatTreeNode.ofTree(this);
407 
408         writeObjectArray(node._nodes.values, out);
409         writeIntArray(node._nodes.childOffsets, out);
410         writeIntArray(node._nodes.childCounts, out);
411     }
412 
413     @SuppressWarnings("rawtypes")
414     static FlatTreeNode read(final ObjectInput in)
415         throws IOException, ClassNotFoundException
416     {
417         return new FlatTreeNode(new Nodes(
418             readObjectArray(in),
419             readIntArray(in),
420             readIntArray(in)
421         ));
422     }
423 
424 }