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 372c0c071fe1..a18e2944f979 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java @@ -29,6 +29,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import static com.intellij.openapi.util.Conditions.not; +import static com.intellij.util.containers.JBIterable.SeparatorOption.*; /** * @author gregsh @@ -299,6 +300,29 @@ public class TreeTraverserTest extends TestCase { assertEquals(Arrays.asList(0, 1, 0, 2, 0, 3, 0, 4, 0, 5), it.flatMap(o -> ContainerUtil.list(0, o)).toList()); } + public void testPartition() { + JBIterable it = JBIterable.of(1, 2, 3, 4, 5); + assertEquals(Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4)), it.partition(2, true).toList()); + assertEquals(Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5)), it.partition(2, false).toList()); + + assertEquals("[[1, 2], [4, 5]]", it.partition(SKIP, o -> o % 3 == 0).map(o -> o.toList()).toList().toString()); + assertEquals("[[1, 2], [3], [4, 5]]", it.partition(EXTRACT, o -> o % 3 == 0).map(o -> o.toList()).toList().toString()); + assertEquals("[[1, 2, 3], [4, 5]]", it.partition(HEAD, o -> o % 3 == 0).map(o -> o.toList()).toList().toString()); + assertEquals("[[1, 2], [3, 4, 5]]", it.partition(TAIL, o -> o % 3 == 0).map(o -> o.toList()).toList().toString()); + assertEquals("[[1, 2, 3, 4], [5]]", it.partition(EXTRACT, o -> o == 5).map(o -> o.toList()).toList().toString()); + assertEquals("[[], [1], [2, 3, 4, 5]]", it.partition(EXTRACT, o -> o == 1).map(o -> o.toList()).toList().toString()); + + assertEquals("[[], [], [], [], []]", it.partition(SKIP, o -> true).map(o -> o.toList()).toList().toString()); + assertEquals("[[1], [2], [3], [4], [5]]", it.partition(HEAD, o -> true).map(o -> o.toList()).toList().toString()); + assertEquals("[[], [1], [2], [3], [4], [5]]", it.partition(TAIL, o -> true).map(o -> o.toList()).toList().toString()); + assertEquals("[[], [1], [], [2], [], [3], [], [4], [], [5]]", it.partition(EXTRACT, o -> true).map(o -> o.toList()).toList().toString()); + + assertEquals(3, it.partition(EXTRACT, o -> o % 3 == 0).size()); + assertEquals(10, it.partition(EXTRACT, o -> true).size()); + + assertEquals(it.partition(2, false).toList(), it.partition(HEAD, o -> o % 2 == 0).map(o -> o.toList()).toList()); + } + // TreeTraversal ---------------------------------------------- @NotNull diff --git a/platform/util/src/com/intellij/util/containers/JBIterable.java b/platform/util/src/com/intellij/util/containers/JBIterable.java index ca6b0f8a414b..a7cae5a7156d 100644 --- a/platform/util/src/com/intellij/util/containers/JBIterable.java +++ b/platform/util/src/com/intellij/util/containers/JBIterable.java @@ -482,6 +482,85 @@ public abstract class JBIterable implements Iterable { return map(function).flatten(Function.ID); } + /** + * Returns a {@code JBIterable} that groups this iterable into lists of the specified size. + * If 'strict' flag is true only groups of size 'n' are returned. + */ + @NotNull + public final JBIterable> partition(final int n, final boolean strict) { + if (n <= 0) throw new IllegalArgumentException(n + " <= 0"); + return intercept(new Function, Iterator>>() { + @Override + public Iterator> fun(Iterator iterator) { + final Iterator orig = iterator; + return new JBIterator>() { + @Override + protected List nextImpl() { + ArrayList next = ContainerUtil.newArrayListWithCapacity(n); + for (E e : once(orig).take(n)) { + next.add(e); + } + return next.isEmpty() || strict && next.size() < n ? stop() : next; + } + }; + } + }); + } + + public enum SeparatorOption {HEAD, TAIL, EXTRACT, SKIP} + + /** + * Returns a {@code JBIterable} that groups this iterable by the specified condition + * without additional memory allocation. + */ + @NotNull + public final JBIterable> partition(final SeparatorOption option, final Condition condition) { + return intercept(new Function, Iterator>>() { + @Override + public Iterator> fun(Iterator iterator) { + final Iterator orig = iterator; + return new JBIterator>() { + List stored; + JBIterator it; + + @Override + protected JBIterable nextImpl() { + // iterate through the previous result fully before proceeding + if (it != null && it.hasNext()) once(it).size(); + it = null; + List sep = stored; + stored = null; + if (option == SeparatorOption.EXTRACT && sep != null) return JBIterable.from(sep); + if (!orig.hasNext()) { + return option == SeparatorOption.TAIL && sep != null ? JBIterable.from(sep) : stop(); + } + it = JBIterator.wrap(orig); + JBIterable next = once(it.takeWhile(new Condition() { + @Override + public boolean value(E e) { + if (!condition.value(e)) return true; + stored = Collections.singletonList(e); + return false; + } + })); + switch (option) { + case HEAD: return next.append(new JBIterable() { + @Override + public Iterator iterator() { + return stored != null ? stored.iterator() : JBIterable.empty().iterator(); + } + }); + case TAIL: return sep != null ? JBIterable.from(sep).append(next) : next; + case EXTRACT: + case SKIP: return next; + default: throw new AssertionError(option); + } + } + }; + } + }); + } + /** * Determines whether this iterable is empty. */ diff --git a/platform/util/src/com/intellij/util/containers/JBIterator.java b/platform/util/src/com/intellij/util/containers/JBIterator.java index 535c56284966..41ed573978ed 100644 --- a/platform/util/src/com/intellij/util/containers/JBIterator.java +++ b/platform/util/src/com/intellij/util/containers/JBIterator.java @@ -60,7 +60,12 @@ public abstract class JBIterator implements Iterator { @NotNull public static JBIterator from(@NotNull final Iterator it) { - return it instanceof JBIterator ? (JBIterator)it : new JBIterator() { + return it instanceof JBIterator ? (JBIterator)it : wrap(it); + } + + @NotNull + static JBIterator wrap(@NotNull final Iterator it) { + return new JBIterator() { @Override protected E nextImpl() { return it.hasNext() ? it.next() : stop(); @@ -71,7 +76,7 @@ public abstract class JBIterator implements Iterator { private Object myCurrent = NONE; private Object myNext = NONE; - private final Op myFirstOp = new Op(null); + private Op myFirstOp = new Op(null); private Op myLastOp = myFirstOp; /** @@ -141,7 +146,7 @@ public abstract class JBIterator implements Iterator { if (myNext != NONE) return; Object o = NONE; for (Op op = myFirstOp; op != null; op = op == null ? myFirstOp : op.nextOp) { - o = op == myFirstOp ? nextImpl() : op.apply(o); + o = op.impl == null ? nextImpl() : op.apply(o); if (myNext == SKIP) { o = myNext = NONE; op = null; @@ -153,42 +158,23 @@ public abstract class JBIterator implements Iterator { @NotNull public final JBIterator transform(@NotNull Function function) { - return addOp(new Op>(function) { - @Override - public Object apply(Object o) { - return impl.fun((E)o); - } - }); + return addOp(true, new TransformOp(function)); } @NotNull public final JBIterator filter(@NotNull Condition condition) { - return addOp(new Op>(condition) { - @Override - public Object apply(Object o) { - return impl.value((E)o) ? o : skip(); - } - }); + return addOp(true, new FilterOp(condition)); } @NotNull public final JBIterator take(int count) { - return takeWhile(new CountDown(count)); + // add first so that the underlying iterator stay on 'count' position + return addOp(myLastOp.impl != null, new WhileOp(new CountDown(count))); } @NotNull public final JBIterator takeWhile(@NotNull Condition condition) { - return addOp(new Op>(condition) { - @Override - public Object apply(Object o) { - return impl.value((E)o) ? o : stop(); - } - - @Override - public String toString() { - return "takeWhile:" + super.toString(); - } - }); + return addOp(true, new WhileOp(condition)); } @NotNull @@ -198,28 +184,19 @@ public abstract class JBIterator implements Iterator { @NotNull public final JBIterator skipWhile(@NotNull final Condition condition) { - return addOp(new Op>(condition) { - - boolean active = true; - - @Override - public Object apply(Object o) { - if (active && condition.value((E)o)) return skip(); - active = false; - return o; - } - - @Override - public String toString() { - return "skipWhile:" + super.toString(); - } - }); + return addOp(true, new SkipOp(condition)); } @NotNull - private T addOp(@NotNull Op op) { - myLastOp.nextOp = op; - myLastOp = myLastOp.nextOp; + private T addOp(boolean last, @NotNull Op op) { + if (last) { + myLastOp.nextOp = op; + myLastOp = myLastOp.nextOp; + } + else { + op.nextOp = myFirstOp; + myFirstOp = op; + } return (T)this; } @@ -301,4 +278,53 @@ public abstract class JBIterator implements Iterator { return cur > 0 && cur-- != 0; } } + + private static class TransformOp extends Op> { + TransformOp(Function function) { + super(function); + } + + @Override + public Object apply(Object o) { + return impl.fun((E)o); + } + } + + private class FilterOp extends Op> { + FilterOp(Condition condition) { + super(condition); + } + + @Override + public Object apply(Object o) { + return impl.value((E)o) ? o : skip(); + } + } + + private class WhileOp extends Op> { + + WhileOp(Condition condition) { + super(condition); + } + @Override + public Object apply(Object o) { + return impl.value((E)o) ? o : stop(); + } + } + + private class SkipOp extends Op> { + boolean active; + + SkipOp(Condition condition) { + super(condition); + active = true; + } + + @Override + public Object apply(Object o) { + if (active && impl.value((E)o)) return skip(); + active = false; + return o; + } + } }