diff --git a/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java b/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java index daa631d312c1..d76a54768f38 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java @@ -15,6 +15,7 @@ */ package com.intellij.util.containers; +import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Condition; import com.intellij.util.Consumer; import com.intellij.util.Function; @@ -146,6 +147,21 @@ public class TreeTraverserTest extends TestCase { }; } + @NotNull + private static JBIterable.StatefulFilter UP_TO(final E o) { + return new JBIterable.StatefulFilter() { + boolean b; + + @Override + public boolean value(E e) { + if (b) return false; + b = Comparing.equal(e, o); + return true; + } + }; + } + + // JBIterable ---------------------------------------------- public void testAppend() { @@ -248,6 +264,47 @@ public class TreeTraverserTest extends TestCase { assertEquals(Arrays.asList(1, 2, 5, 6, 7, 3, 8, 9, 10, 4, 11, 12, 13), numTraverser(TreeTraversal.PRE_ORDER_DFS).fun(1).toList()); } + public void testSimpleInterlacedDfs() { + assertEquals(Arrays.asList(1, 2, 5, 3, 6, 4, 8, 7, 9, 11, 10, 12, 13), numTraverser(TreeTraversal.INTERLEAVED_DFS).fun(1).toList()); + } + + public void testCyclicInterlacedDfs() { + Function> traversal = TreeTraversal.INTERLEAVED_DFS.traversal(Functions.fromMap( + ContainerUtil.>immutableMapBuilder() + .put(1, Arrays.asList(1, 2)) + .put(2, Arrays.asList(1, 2, 3)) + .put(3, Arrays.asList()).build())); + assertEquals(Arrays.asList(1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3), traversal.fun(1).takeWhile(UP_TO(3)).toList()); + } + + public void testIndefiniteCyclicInterlacedDfs() { + Function> traversal = TreeTraversal.INTERLEAVED_DFS.traversal( + new Function>() { + @Override + public Iterable fun(Integer integer) { + JBIterable it = JBIterable.generate(1, INCREMENT).takeWhile(UP_TO(integer + 1)); + // 1: no repeat + return it; + // 2: repeat indefinitely: all seq + //return JBIterable.generate(it, Functions.id()).flatten(Functions.id()); + // 3: repeat indefinitely: self-cycle + //return it.append(JBIterable.generate(integer, Functions.id())); + } + }); + JBIterable counts = JBIterable.generate(1, INCREMENT).transform(new Function() { + @Override + public Integer fun(Integer integer) { + return traversal.fun(1).takeWhile(UP_TO(integer)).size(); + } + }); + // 1: no repeat + assertEquals(Arrays.asList(1, 4, 13, 39, 117, 359, 1134, 3686, 12276, 41708), counts.take(10).toList()); + // 2: repeat all seq + //assertEquals(Arrays.asList(1, 4, 19, 236), counts.take(4).toList()); + // 2: repeat self-cycle + //assertEquals(Arrays.asList(1, 4, 19, 236), counts.take(4).toList()); + } + public void testSimplePreOrderDfsBacktrace() { List backDfs = Collections.emptyList(); for (TreeTraversal.TracingIt it = numTraverser2(TreeTraversal.PRE_ORDER_DFS).fun(1).typedIterator(); it.hasNext(); ) { diff --git a/platform/util/src/com/intellij/util/containers/TreeTraversal.java b/platform/util/src/com/intellij/util/containers/TreeTraversal.java index c91c08e56f1d..519e221845d4 100644 --- a/platform/util/src/com/intellij/util/containers/TreeTraversal.java +++ b/platform/util/src/com/intellij/util/containers/TreeTraversal.java @@ -104,16 +104,28 @@ public abstract class TreeTraversal { } public static abstract class It extends JBIterator { + protected final Function> tree; + protected It(Function> tree) { + this.tree = tree; + } } public static abstract class TracingIt extends It { @Nullable - public abstract T parent(); + public T parent() { + throw new UnsupportedOperationException(); + } @NotNull - public abstract JBIterable backtrace(); + public JBIterable backtrace() { + throw new UnsupportedOperationException(); + } + + protected TracingIt(Function> tree) { + super(tree); + } } public static abstract class GuidedIt extends It { @@ -126,9 +138,12 @@ public abstract class TreeTraversal { public abstract GuidedIt setGuide(Consumer> guide); public abstract GuidedIt queueNext(T child); - public abstract GuidedIt queueLast(T child); public abstract GuidedIt result(T node); + public abstract GuidedIt queueLast(T child); + protected GuidedIt(Function> tree) { + super(tree); + } } @NotNull @@ -182,6 +197,24 @@ public abstract class TreeTraversal { } }; + /** + * Returns an iterator over the nodes in a tree structure, using interlaced pre-order + * traversal. That is, all paths are traversed in an interlaced manner that is suitable + * for infinite and cyclic graphs + * and each node's subtrees are traversed before the node itself is returned. + *

+ *

No guarantees are made about the behavior of the traversal when nodes change while + * iteration is in progress or when the iterators generated by {@code tree} are advanced. + */ + @NotNull + public static final TreeTraversal INTERLEAVED_DFS = new TreeTraversal("INTERLEAVED_DFS") { + @NotNull + @Override + public It createIterator(@NotNull Iterable roots, @NotNull Function> tree) { + return new InterleavedIt(roots, tree); + } + }; + /** * Returns an iterator over the nodes in a tree structure, using breadth-first * traversal. That is, all the nodes of depth 0 are returned, then depth 1, then 2, and so on. @@ -216,125 +249,160 @@ public abstract class TreeTraversal { } }; - // ----------------------------------------------------------------------------- // Iterators: DFS // ----------------------------------------------------------------------------- - private abstract static class DfsIt extends TracingIt { - final ArrayDeque> stack = new ArrayDeque>(); + private abstract static class DfsIt> extends TracingIt { + + H last; + + protected DfsIt(Function> tree) { + super(tree); + } @Nullable public T parent() { - if (stack.isEmpty()) throw new NoSuchElementException(); - Iterator> it = stack.descendingIterator(); - it.next(); - return it.hasNext() ? it.next().node : null; + if (last == null) throw new NoSuchElementException(); + + H p = last.parent; + return p == null ? null : p.node; } @NotNull public JBIterable backtrace() { - if (stack.isEmpty()) throw new NoSuchElementException(); - return new JBIterable>() { - @Override - public Iterator> iterator() { - return stack.descendingIterator(); - } - }.transform(P.toNode()).filter(Condition.NOT_NULL); + if (last == null) throw new NoSuchElementException(); + return JBIterable.generate(last, P.toPrev()).transform(P.toNode()).filter(Condition.NOT_NULL); } } - private final static class PreOrderIt extends DfsIt { - - final Function> tree; + private final static class PreOrderIt extends DfsIt> { PreOrderIt(@NotNull Iterable roots, Function> tree) { - this.tree = tree; - stack.addLast(P.create(roots)); + super(tree); + last = P1.create(roots); } @Override public T nextImpl() { - while (!stack.isEmpty()) { - Iterator it = stack.getLast().iterator(tree); + while (last != null) { + Iterator it = last.iterator(tree); if (it.hasNext()) { T result = it.next(); - stack.addLast(P.create(result)); + last = last.add(P1.create(result)); return result; } else { - stack.removeLast(); + last = last.remove(); } } return stop(); } } - private static final class PostOrderIt extends DfsIt { - - final Function> tree; + private static final class PostOrderIt extends DfsIt> { PostOrderIt(@NotNull Iterable roots, Function> tree) { - this.tree = tree; + super(tree); for (T root : roots) { - stack.addLast(P.create(root)); + P1 p = P1.create(root); + last = last == null ? p : last.add(p); } } @Override public T nextImpl() { - while (!stack.isEmpty()) { - Iterator it = stack.getLast().iterator(tree); + while (last != null) { + Iterator it = last.iterator(tree); if (it.hasNext()) { T result = it.next(); - stack.addLast(P.create(result)); + last = last.add(P1.create(result)); } else { - return stack.removeLast().node; + T result = last.node; + last = last.remove(); + return result; } } return stop(); } } - private final static class LeavesDfsIt extends DfsIt { - - final Function> tree; + private final static class LeavesDfsIt extends DfsIt> { LeavesDfsIt(@NotNull Iterable roots, Function> tree) { - this.tree = tree; - stack.addLast(P.create(roots)); + super(tree); + last = P1.create(roots); } @Override public T nextImpl() { - while (!stack.isEmpty()) { - P top = stack.getLast(); + while (last != null) { + P1 top = last; if (top.iterator(tree).hasNext() && !top.empty) { T child = top.iterator(tree).next(); - stack.addLast(P.create(child)); + last = last.add(P1.create(child)); } else { - stack.removeLast(); - if (top.empty) return stack.isEmpty() ? stop() : top.node; + last = last.remove(); + if (top.empty) return last == null ? stop() : top.node; } } return stop(); } } + private final static class InterleavedIt extends DfsIt> { + + P2 cur, max; + + InterleavedIt(@NotNull Iterable roots, Function> tree) { + super(tree); + last = P2.create(roots); + cur = max = last; + } + + @Override + public T nextImpl() { + while (last != null) { + if (cur == null) { + cur = max; + max = max.next; + } + Iterator it = cur.iterator(tree); + if (it.hasNext()) { + T result = it.next(); + last = last.add(P2.create(result)); + last.parent = cur; + cur = cur.prev; + if (max == null) { + max = last; + } + return result; + } + else { + if (cur == last) { + last = cur.prev; + } + cur = cur.remove(); + } + } + return stop(); + } + } + + // ----------------------------------------------------------------------------- // Iterators: BFS // ----------------------------------------------------------------------------- private static final class PlainBfsIt extends It { - final Function> tree; final ArrayDeque queue = new ArrayDeque(); - P top; + P1 top; PlainBfsIt(@NotNull Iterable roots, Function> tree) { - this.tree = tree; + super(tree); JBIterable.from(roots).addAllTo(queue); } @@ -345,18 +413,17 @@ public abstract class TreeTraversal { top = null; } if (queue.isEmpty()) return stop(); - top = P.create(queue.remove()); + top = P1.create(queue.remove()); return top.node; } } - private static final class LeavesBfsIt extends It { + private static final class LeavesBfsIt extends TracingIt { - final Function> tree; final ArrayDeque queue = new ArrayDeque(); LeavesBfsIt(@NotNull Iterable roots, Function> tree) { - this.tree = tree; + super(tree); JBIterable.from(roots).addAllTo(queue); } @@ -375,13 +442,12 @@ public abstract class TreeTraversal { private final static class TracingBfsIt extends TracingIt { - final Function> tree; final ArrayDeque queue = new ArrayDeque(); final Map paths = ContainerUtil.newTroveMap(ContainerUtil.identityStrategy()); - P top; + P1 top; TracingBfsIt(@NotNull Iterable roots, Function> tree) { - this.tree = tree; + super(tree); JBIterable.from(roots).addAllTo(queue); } @@ -396,7 +462,7 @@ public abstract class TreeTraversal { top = null; } if (queue.isEmpty()) return stop(); - top = P.create(queue.remove()); + top = P1.create(queue.remove()); return top.node; } @@ -434,15 +500,14 @@ public abstract class TreeTraversal { // Misc // ----------------------------------------------------------------------------- private static final class GuidedItImpl extends GuidedIt { - final ArrayDeque> stack = new ArrayDeque>(); - final Function> tree; + P1 first, last; Consumer> guide; T curResult; GuidedItImpl(@NotNull Iterable roots, Function> tree) { - this.tree = tree; - stack.addLast(P.create(roots)); + super(tree); + first = last = P1.create(roots); } public GuidedIt setGuide(Consumer> guide) { @@ -451,12 +516,12 @@ public abstract class TreeTraversal { } public GuidedIt queueNext(T child) { - if (child != null) stack.addLast(P.create(child)); + if (child != null) last = last.add(P1.create(child)); return this; } public GuidedIt queueLast(T child) { - if (child != null) stack.addFirst(P.create(child)); + if (child != null) first = first.addBefore(P1.create(child)); return this; } @@ -468,8 +533,8 @@ public abstract class TreeTraversal { @Override public T nextImpl() { if (guide == null) return stop(); - while (!stack.isEmpty()) { - P top = stack.getLast(); + while (last != null) { + P top = last; Iterator it = top.iterator(tree); boolean hasNext = it.hasNext(); curResult = null; @@ -481,7 +546,7 @@ public abstract class TreeTraversal { guide.consume(this); } if (!hasNext) { - stack.removeLast(); + last = last.remove(); } if (curResult != null) { return curResult; @@ -491,45 +556,113 @@ public abstract class TreeTraversal { } } - private static class P { + private static class P> { T node; Iterable itle; Iterator it; boolean empty; - Iterator iterator(@NotNull Function> tree) { + Self parent; + + static > Self create(Self p, T node) { + p.node = node; + return p; + } + + static > Self create(Self p, Iterable it) { + p.itle = it; + return p; + } + + + final Iterator iterator(@NotNull Function> tree) { if (it != null) return it; it = iterable(tree).iterator(); empty = itle == null || !it.hasNext(); return it; } - Iterable iterable(@NotNull Function> tree) { + final Iterable iterable(@NotNull Function> tree) { return itle != null ? itle : JBIterable.from(itle = tree.fun(node)); } - static P create(T node) { - P p = new P(); - p.node = node; - return p; - } + /** @noinspection unchecked */ + static Function, T> toNode() { return TO_NODE; } + /** @noinspection unchecked */ + static Function, P> toPrev() { return TO_PREV; } - static P create(Iterable it) { - P p = new P(); - p.itle = it; - return p; - } - - static Function, T> toNode() { - //noinspection unchecked - return TO_NODE; - } - - static final Function TO_NODE = new Function, Object>() { + static final Function TO_NODE = new Function, Object>() { @Override - public Object fun(P tp) { + public Object fun(P tp) { return tp.node; } }; + static final Function TO_PREV = new Function.Mono>() { + @Override + public P fun(P tp) { + return tp.parent; + } + }; + } + + private static final class P1 extends P> { + + static P1 create(T node) { return create(new P1(), node); } + static P1 create(Iterable it) { return create(new P1(), it); } + + P1 add(@NotNull P1 next) { + next.parent = this; + return next; + } + + P1 addBefore(@NotNull P1 next) { + next.parent = null; + this.parent = next; + return next; + } + + P1 remove() { + P1 p = parent; + parent = null; + return p; + } + + @Override + public String toString() { + int h = 0; + for (P1 p = parent; p != null; p = p.parent) h++; + return h + ": " + node; + } + } + + private static final class P2 extends P> { + P2 next, prev; + + static P2 create(T node) { return create(new P2(), node); } + static P2 create(Iterable it) { return create(new P2(), it); } + + P2 add(@NotNull P2 next) { + next.next = this.next; + next.prev = this; + this.next = next; + return next; + } + + P2 remove() { + P2 p = prev; + P2 n = next; + prev = next = null; + if (p != null) p.next = n; + if (n != null) n.prev = p; + return p; + } + + @Override + public String toString() { + int h = 0, t = 0; + for (P2 p = prev; p != null; p = p.prev) h++; + for (P2 p = next; p != null; p = p.next) t++; + return h + " of " + (h + t + 1) + ": " + node; + } } }