瀏覽代碼

resolve #104 CQuery: BFS

Ranides Atterwim 1 年之前
父節點
當前提交
019078aea5

+ 55 - 5
assira.core/src/main/java/net/ranides/assira/collection/query/CQuery.java

@@ -20,6 +20,7 @@ import net.ranides.assira.collection.lists.LazyList;
 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.CQTreeBFS;
 import net.ranides.assira.collection.query.derived.CQTreeDFS;
 import net.ranides.assira.functional.Consumers.EachConsumer;
 import net.ranides.assira.functional.Functions.EachFunction;
@@ -1622,17 +1623,40 @@ 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 <T> T
+         * @return builder
+         */
+        @SuppressWarnings("unchecked")
+        public <T> TreeBuilder<T> tree() {
+            return TreeBuilder.INSTANCE;
+        }
+
+    }
+
+    /**
+     * This factory class allows to create new Queries from tree
+     *
+     * @param <F> F
+     */
+    class TreeBuilder<F> {
+
+        @SuppressWarnings({"rawtypes"})
+        protected static final TreeBuilder INSTANCE = new TreeBuilder();
+
         /**
          * Creates stream traversing provided tree nodes.
          *
          *
          * @param <T> T
          * @param root root
- * @param children children
- * @param isTerm isTerm
- * @return query
+         * @param children children
+         * @param isTerm isTerm
+         * @return query
          */
-        public <T> CQuery<T> tree(Supplier<T> root, Function<T, CQuery<T>> children, Predicate<T> isTerm) {
+        public <T> CQuery<T> dfs(Supplier<T> root, Function<T, CQuery<T>> children, Predicate<T> isTerm) {
             return new CQTreeDFS<>(root, children, isTerm, null);
         }
 
@@ -1644,10 +1668,36 @@ public interface CQuery<T> extends Iterable<T> {
          * @return query
          * @param <T> T
          */
-        public <T> CQuery<T> tree(Supplier<T> root, Function<T, CQuery<T>> children) {
+        public <T> CQuery<T> dfs(Supplier<T> root, Function<T, CQuery<T>> children) {
             return new CQTreeDFS<>(root, children, node -> false, null);
         }
 
+        /**
+         * Creates stream traversing provided tree nodes.
+         *
+         *
+         * @param <T> T
+         * @param root root
+         * @param children children
+         * @param isTerm isTerm
+         * @return query
+         */
+        public <T> CQuery<T> bfs(Supplier<T> root, Function<T, CQuery<T>> children, Predicate<T> isTerm) {
+            return new CQTreeBFS<>(root, children, isTerm, null);
+        }
+
+        /**
+         * Creates stream traversing provided tree nodes.
+         *
+         * @param root root
+         * @param children children
+         * @return query
+         * @param <T> T
+         */
+        public <T> CQuery<T> bfs(Supplier<T> root, Function<T, CQuery<T>> children) {
+            return new CQTreeBFS<>(root, children, node -> false, null);
+        }
+
     }
 
     /**

+ 132 - 0
assira.core/src/main/java/net/ranides/assira/collection/query/derived/CQTreeBFS.java

@@ -0,0 +1,132 @@
+package net.ranides.assira.collection.query.derived;
+
+import lombok.RequiredArgsConstructor;
+import net.ranides.assira.collection.iterators.ForwardIterator;
+import net.ranides.assira.collection.iterators.IteratorUtils;
+import net.ranides.assira.collection.query.CQuery;
+import net.ranides.assira.collection.query.CQueryAbstract;
+import net.ranides.assira.collection.query.support.BaseEach;
+import net.ranides.assira.collection.query.support.BaseIterable;
+import net.ranides.assira.functional.Consumers;
+import net.ranides.assira.functional.Predicates;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+/**
+ * Stream implementation for tree traversal.
+ * It uses BFS 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
+ */
+@RequiredArgsConstructor
+public class CQTreeBFS<T> extends CQueryAbstract<T> {
+
+    protected final Supplier<T> root;
+
+    protected final Function<T, CQuery<T>> children;
+
+    protected final Predicate<T> isTerm;
+
+    protected final Supplier<? extends RuntimeException> message;
+
+    @Override
+    public Supplier<? extends RuntimeException> message() {
+        return message;
+    }
+
+    @Override
+    public CQuery<T> message(Supplier<? extends RuntimeException> message) {
+        return new CQTreeBFS<>(root, children, isTerm, message);
+    }
+
+    @Override
+    public CQuery<T> parallel() {
+        return this;
+    }
+
+    @Override
+    public CQuery<T> sequential() {
+        return this;
+    }
+
+    @Override
+    public boolean isParallel() {
+        return false;
+    }
+
+    @Override
+    public Stream<T> stream() {
+        return BaseIterable.stream(this);
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return IteratorUtils.withCause(new TreeIterator(root.get()), message);
+    }
+
+    @Override
+    public void forEach(Consumer<? super T> consumer) {
+        BaseIterable.forEach(this, consumer);
+    }
+
+    @Override
+    public void forEach(Consumers.EachConsumer<? super T> consumer) {
+        BaseIterable.forEach(this, consumer);
+    }
+
+    @Override
+    public boolean whileEach(Predicate<? super T> consumer) {
+        return BaseIterable.whileEach(this, consumer);
+    }
+
+    @Override
+    public boolean whileEach(Predicates.EachPredicate<? super T> consumer) {
+        return BaseIterable.whileEach(this, consumer);
+    }
+
+    @Override
+    public int size() {
+        return BaseEach.size(this);
+    }
+
+    private final class TreeIterator extends ForwardIterator<T> {
+
+        private final LinkedList<Iterator<T>> window;
+
+        public TreeIterator(T node) {
+            this.window = new LinkedList<>();
+            this.window.add(Collections.singletonList(node).iterator());
+        }
+
+        @Override
+        protected boolean next(Consumer<? super T> action) {
+            while(!window.isEmpty()) {
+                Iterator<T> nodes = window.peekFirst();
+                if(nodes.hasNext()) {
+                    T node = nodes.next();
+                    action.accept(node);
+                    if(!isTerm.test(node)) {
+                        window.addLast(children.apply(node).iterator());
+                    }
+                    return true;
+                } else {
+                    window.removeFirst();
+                }
+            }
+            return false;
+        }
+    }
+
+}

+ 135 - 0
assira.core/src/test/java/net/ranides/assira/collection/query/derived/CQTreeBFSTest.java

@@ -0,0 +1,135 @@
+package net.ranides.assira.collection.query.derived;
+
+import lombok.RequiredArgsConstructor;
+import net.ranides.assira.collection.iterators.IteratorUtils;
+import net.ranides.assira.collection.query.CQuery;
+import net.ranides.test.CQueryAssert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class CQTreeBFSTest {
+
+    private final MyNode tree = new MyNode(
+        "1", Arrays.asList(
+        new MyNode("1/1", Arrays.asList(
+            new MyNode("1/1/1", Arrays.asList()),
+            new MyNode("1/1/2", Arrays.asList()),
+            new MyNode("1/1/3", Arrays.asList()),
+            new MyNode("1/1/4", Arrays.asList())
+        )),
+        new MyNode("1/2", Arrays.asList(
+            new MyNode("1/2/1", Arrays.asList()),
+            new MyNode("1/2/2", Arrays.asList(
+                new MyNode("1/2/2/1", Arrays.asList()),
+                new MyNode("1/2/2/2", Arrays.asList()),
+                new MyNode("1/2/2/3", Arrays.asList())
+            )),
+            new MyNode("1/2/3", Arrays.asList())
+        )),
+        new MyNode("1/3", Arrays.asList(
+            new MyNode("1/3/1", Arrays.asList()),
+            new MyNode("1/3/2", Arrays.asList()),
+            new MyNode("1/3/3", Arrays.asList()),
+            new MyNode("1/3/4", Arrays.asList()),
+            new MyNode("1/3/5", Arrays.asList())
+        ))
+    )
+    );
+
+    private final List<String> bfs = Arrays.asList(
+        "1",
+        "1/1",
+        "1/2",
+        "1/3",
+        "1/1/1",
+        "1/1/2",
+        "1/1/3",
+        "1/1/4",
+        "1/2/1",
+        "1/2/2",
+        "1/2/3",
+        "1/3/1",
+        "1/3/2",
+        "1/3/3",
+        "1/3/4",
+        "1/3/5",
+        "1/2/2/1",
+        "1/2/2/2",
+        "1/2/2/3"
+    );
+
+    @Test
+    public void testBasic() {
+        CQuery<MyNode> query = CQuery.from().tree().bfs(() -> tree, node -> CQuery.from(node.nodes));
+        CQueryAssert.assertEqualsNull(bfs, query.map(v -> v.value));
+    }
+
+    @Test
+    public void testForEach() {
+        CQuery<MyNode> query = CQuery.from().tree().bfs(() -> tree, node -> CQuery.from(node.nodes));
+
+        List<String> out = new ArrayList<>();
+        query.forEach(v -> out.add(v.value));
+
+        assertEquals(bfs, out);
+    }
+
+    @Test
+    public void testWhileEach() {
+        CQuery<MyNode> query = CQuery.from().tree().bfs(() -> 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);
+        }));
+
+        List<String> out3 = new ArrayList<>();
+        assertFalse(query.whileEach(v -> {
+            out3.add(v.value);
+            return !"1/2/2/2".equals(v.value);
+        }));
+
+        assertEquals(bfs, out1);
+        assertEquals(bfs.subList(0, 13), out2);
+        assertEquals(bfs.subList(0, 18), out3);
+    }
+
+    @Test
+    public void testIterator() {
+        CQuery<MyNode> query = CQuery.from().tree().bfs(() -> tree, node -> CQuery.from(node.nodes));
+
+        List<String> out = new ArrayList<>();
+        Iterator<MyNode> itr = query.iterator();
+        while (itr.hasNext()) {
+            out.add(itr.next().value);
+        }
+        assertEquals(bfs, out);
+    }
+
+    @Test
+    public void testList() {
+        CQuery<MyNode> query = CQuery.from().tree().bfs(() -> tree, node -> CQuery.from(node.nodes));
+
+        List<String> out = query.map(v -> v.value).list();
+        assertEquals(bfs, out);
+    }
+
+    @RequiredArgsConstructor
+    private static class MyNode {
+        final String value;
+        final List<MyNode> nodes;
+    }
+}

+ 21 - 7
assira.core/src/test/java/net/ranides/assira/collection/query/derived/CQTreeDFSTest.java

@@ -25,7 +25,11 @@ public class CQTreeDFSTest {
             )),
             new MyNode("1/2", Arrays.asList(
                 new MyNode("1/2/1", Arrays.asList()),
-                new MyNode("1/2/2", Arrays.asList()),
+                new MyNode("1/2/2", Arrays.asList(
+                    new MyNode("1/2/2/1", Arrays.asList()),
+                    new MyNode("1/2/2/2", Arrays.asList()),
+                    new MyNode("1/2/2/3", Arrays.asList())
+                )),
                 new MyNode("1/2/3", Arrays.asList())
             )),
             new MyNode("1/3", Arrays.asList(
@@ -48,6 +52,9 @@ public class CQTreeDFSTest {
         "1/2",
         "1/2/1",
         "1/2/2",
+        "1/2/2/1",
+        "1/2/2/2",
+        "1/2/2/3",
         "1/2/3",
         "1/3",
         "1/3/1",
@@ -59,13 +66,13 @@ public class CQTreeDFSTest {
 
     @Test
     public void testBasic() {
-        CQuery<MyNode> query = CQuery.from().tree(() -> tree, node -> CQuery.from(node.nodes));
+        CQuery<MyNode> query = CQuery.from().tree().dfs(() -> tree, node -> CQuery.from(node.nodes));
         CQueryAssert.assertEqualsNull(dfs, query.map(v -> v.value));
     }
 
     @Test
     public void testForEach() {
-        CQuery<MyNode> query = CQuery.from().tree(() -> tree, node -> CQuery.from(node.nodes));
+        CQuery<MyNode> query = CQuery.from().tree().dfs(() -> tree, node -> CQuery.from(node.nodes));
 
         List<String> out = new ArrayList<>();
         query.forEach(v -> out.add(v.value));
@@ -75,7 +82,7 @@ public class CQTreeDFSTest {
 
     @Test
     public void testWhileEach() {
-        CQuery<MyNode> query = CQuery.from().tree(() -> tree, node -> CQuery.from(node.nodes));
+        CQuery<MyNode> query = CQuery.from().tree().dfs(() -> tree, node -> CQuery.from(node.nodes));
 
         List<String> out1 = new ArrayList<>();
         assertTrue(query.whileEach(v -> {
@@ -89,13 +96,20 @@ public class CQTreeDFSTest {
             return !"1/3/2".equals(v.value);
         }));
 
+        List<String> out3 = new ArrayList<>();
+        assertFalse(query.whileEach(v -> {
+            out3.add(v.value);
+            return !"1/2/2/3".equals(v.value);
+        }));
+
         assertEquals(dfs, out1);
-        assertEquals(dfs.subList(0, 13), out2);
+        assertEquals(dfs.subList(0, 16), out2);
+        assertEquals(dfs.subList(0, 12), out3);
     }
 
     @Test
     public void testIterator() {
-        CQuery<MyNode> query = CQuery.from().tree(() -> tree, node -> CQuery.from(node.nodes));
+        CQuery<MyNode> query = CQuery.from().tree().dfs(() -> tree, node -> CQuery.from(node.nodes));
 
         List<String> out = new ArrayList<>();
         Iterator<MyNode> itr = query.iterator();
@@ -107,7 +121,7 @@ public class CQTreeDFSTest {
 
     @Test
     public void testList() {
-        CQuery<MyNode> query = CQuery.from().tree(() -> tree, node -> CQuery.from(node.nodes));
+        CQuery<MyNode> query = CQuery.from().tree().dfs(() -> tree, node -> CQuery.from(node.nodes));
 
         List<String> out = query.map(v -> v.value).list();
         assertEquals(dfs, out);