Browse Source

resolve #96: CTreeQuery

Ranides Atterwim 3 years ago
parent
commit
a905db3bef

+ 38 - 0
assira.core/src/main/java/net/ranides/assira/collection/query/CQuery.java

@@ -21,6 +21,7 @@ import net.ranides.assira.collection.lists.IntList;
 import net.ranides.assira.collection.lists.NativeArrayList;
 import net.ranides.assira.collection.lists.VirtualList;
 import net.ranides.assira.collection.query.base.*;
+import net.ranides.assira.collection.query.derived.CQTreeDFS;
 import net.ranides.assira.functional.Consumers.EachConsumer;
 import net.ranides.assira.functional.Functions.EachFunction;
 import net.ranides.assira.functional.Predicates.EachPredicate;
@@ -1439,6 +1440,43 @@ public interface CQuery<T> extends Iterable<T> {
             return from(VirtualList.of((int)Math.max(0, end-begin), v -> begin+v));
         }
 
+        /**
+         * Creates stream traversing provided tree nodes.
+         *
+         * @param root root
+         * @param isTerm isTerm
+         * @param children children
+         * @return query
+         * @param <T> T
+         */
+        public <T> CQuery<T> tree(Supplier<T> root, Predicate<T> isTerm, Function<T, CQuery<T>> children) {
+            return new CQTreeDFS<>(root, isTerm, children);
+        }
+
+        /**
+         * Creates stream traversing provided tree nodes.
+         *
+         * @param root root
+         * @param children children
+         * @return query
+         * @param <T> T
+         */
+        public <T> CQuery<T> tree(Supplier<T> root, Function<T, CQuery<T>> children) {
+            return new CQTreeDFS<>(root, node -> false, children);
+        }
+
+        /**
+         * Creates stream traversing provided tree nodes.
+         *
+         * @param root root
+         * @param children children
+         * @return query
+         * @param <T> T
+         */
+        public <T> CQuery<T> tree(T root, Function<T, CQuery<T>> children) {
+            return new CQTreeDFS<>(() -> root, node -> false, children);
+        }
+
     }
 
     /**

+ 39 - 8
assira.core/src/main/java/net/ranides/assira/collection/query/derived/CQTreeDFS.java

@@ -1,6 +1,5 @@
 package net.ranides.assira.collection.query.derived;
 
-import lombok.RequiredArgsConstructor;
 import net.ranides.assira.collection.iterators.ForwardIterator;
 import net.ranides.assira.collection.query.CQuery;
 import net.ranides.assira.collection.query.CQueryAbstract;
@@ -12,7 +11,6 @@ import net.ranides.assira.functional.Predicates;
 
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.Stack;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -20,15 +18,36 @@ import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
+/**
+ * Stream implementation for tree traversal.
+ * It uses DFS algorithm.
+ *
+ * It can visit any tree, as long as it can provide 3 operations:
+ *   - root node
+ *   - list of children of provided node
+ *   - predicate returning true, if node is terminal
+ *
+ * @param <T> T
+ */
 public class CQTreeDFS<T> extends CQueryAbstract<T> {
 
     protected final Supplier<T> root;
 
-    protected final Function<T, CQuery<T>> splitter;
+    protected final Predicate<T> isTerm;
 
-    public CQTreeDFS(Supplier<T> root, Function<T, CQuery<T>> splitter) {
+    protected final Function<T, CQuery<T>> children;
+
+    /**
+     * Creates new stream
+     *
+     * @param root root
+     * @param isTerm isTerm
+     * @param children children
+     */
+    public CQTreeDFS(Supplier<T> root, Predicate<T> isTerm, Function<T, CQuery<T>> children) {
         this.root = root;
-        this.splitter = splitter;
+        this.isTerm = isTerm;
+        this.children = children;
     }
 
     @Override
@@ -46,6 +65,11 @@ public class CQTreeDFS<T> extends CQueryAbstract<T> {
         return false;
     }
 
+    @Override
+    public boolean hasFastEach() {
+        return true;
+    }
+
     @Override
     public Stream<T> stream() {
         return BaseIterable.stream(this);
@@ -80,14 +104,19 @@ public class CQTreeDFS<T> extends CQueryAbstract<T> {
 
     private void forEach0(T node, Consumer<? super T> consumer) {
         consumer.accept(node);
-        splitter.apply(node).forEach(child -> forEach0(child, consumer));
+        if(!isTerm.test(node)) {
+            children.apply(node).forEach(child -> forEach0(child, consumer));
+        }
     }
 
     private boolean whileEach0(T node, Predicate<? super T> consumer) {
         if(!consumer.test(node)) {
             return false;
         }
-        return splitter.apply(node).whileEach(child -> whileEach0(child, consumer));
+        if(!isTerm.test(node)) {
+            return children.apply(node).whileEach(child -> whileEach0(child, consumer));
+        }
+        return true;
     }
 
     @Override
@@ -111,7 +140,9 @@ public class CQTreeDFS<T> extends CQueryAbstract<T> {
                 if (nodes.hasNext()) {
                     T node = nodes.next();
                     action.accept(node);
-                    window.push( splitter.apply(node).iterator() );
+                    if(!isTerm.test(node)) {
+                        window.push(children.apply(node).iterator());
+                    }
                     return true;
                 } else {
                     window.pop();

+ 67 - 6
assira.core/src/test/java/net/ranides/assira/collection/query/derived/CQTreeDFSTest.java

@@ -2,8 +2,11 @@ package net.ranides.assira.collection.query.derived;
 
 import lombok.RequiredArgsConstructor;
 import net.ranides.assira.collection.query.CQuery;
+import net.ranides.test.CQueryAssert;
+import org.junit.Assert;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
@@ -12,7 +15,7 @@ import static org.junit.Assert.*;
 
 public class CQTreeDFSTest {
 
-    MyNode root = new MyNode(
+    private final MyNode tree = new MyNode(
         "1", Arrays.asList(
             new MyNode("1/1", Arrays.asList(
                 new MyNode("1/1/1", Arrays.asList()),
@@ -35,21 +38,79 @@ public class CQTreeDFSTest {
         )
     );
 
+    private final List<String> dfs = Arrays.asList(
+        "1",
+        "1/1",
+        "1/1/1",
+        "1/1/2",
+        "1/1/3",
+        "1/1/4",
+        "1/2",
+        "1/2/1",
+        "1/2/2",
+        "1/2/3",
+        "1/3",
+        "1/3/1",
+        "1/3/2",
+        "1/3/3",
+        "1/3/4",
+        "1/3/5"
+        );
+
+    @Test
+    public void testBasic() {
+        CQuery<MyNode> query = CQuery.from().tree(tree, node -> CQuery.from(node.nodes));
+        CQueryAssert.assertEqualsNull(dfs, query.map(v -> v.value));
+    }
+
     @Test
     public void testForEach() {
-        CQTreeDFS<MyNode> dfs = new CQTreeDFS<>(() -> root, node -> CQuery.from(node.nodes));
+        CQuery<MyNode> query = CQuery.from().tree(tree, node -> CQuery.from(node.nodes));
 
-        dfs.forEach(v -> System.out.println(v.value));
+        List<String> out = new ArrayList<>();
+        query.forEach(v -> out.add(v.value));
+
+        assertEquals(dfs, out);
+    }
+
+    @Test
+    public void testWhileEach() {
+        CQuery<MyNode> query = CQuery.from().tree(tree, node -> CQuery.from(node.nodes));
+
+        List<String> out1 = new ArrayList<>();
+        assertTrue(query.whileEach(v -> {
+            out1.add(v.value);
+            return true;
+        }));
+
+        List<String> out2 = new ArrayList<>();
+        assertFalse(query.whileEach(v -> {
+            out2.add(v.value);
+            return !"1/3/2".equals(v.value);
+        }));
+
+        assertEquals(dfs, out1);
+        assertEquals(dfs.subList(0, 13), out2);
     }
 
     @Test
     public void testIterator() {
-        CQTreeDFS<MyNode> dfs = new CQTreeDFS<>(() -> root, node -> CQuery.from(node.nodes));
+        CQuery<MyNode> query = CQuery.from().tree(tree, node -> CQuery.from(node.nodes));
 
-        Iterator<MyNode> itr = dfs.iterator();
+        List<String> out = new ArrayList<>();
+        Iterator<MyNode> itr = query.iterator();
         while (itr.hasNext()) {
-            System.out.println(itr.next().value);
+            out.add(itr.next().value);
         }
+        assertEquals(dfs, out);
+    }
+
+    @Test
+    public void testList() {
+        CQuery<MyNode> query = CQuery.from().tree(tree, node -> CQuery.from(node.nodes));
+
+        List<String> out = query.map(v -> v.value).list();
+        assertEquals(dfs, out);
     }
 
     @RequiredArgsConstructor