From c6a2d5b3262ca358d357435fcaa2ff191d39b314 Mon Sep 17 00:00:00 2001 From: Johannes Link Date: Wed, 15 Nov 2023 10:56:44 +0100 Subject: [PATCH] Fixed out of memory error in shrinking. See https://github.com/jqwik-team/jqwik/issues/525 --- .../jqwik/engine/support/Combinatorics.java | 48 ++++++++++++++++--- .../engine/support/CombinatoricsTests.java | 19 ++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/engine/src/main/java/net/jqwik/engine/support/Combinatorics.java b/engine/src/main/java/net/jqwik/engine/support/Combinatorics.java index d5d0d2fd7..19a55c413 100644 --- a/engine/src/main/java/net/jqwik/engine/support/Combinatorics.java +++ b/engine/src/main/java/net/jqwik/engine/support/Combinatorics.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.*; +import java.util.function.*; import java.util.stream.*; import net.jqwik.api.*; @@ -81,15 +82,48 @@ public static Stream> distinctPairs(int maxExclusive) { if (maxExclusive < 2) { return Stream.empty(); } - List> pairs = new ArrayList<>(); - for (int i = 0; i < maxExclusive; i++) { - for (int j = i + 1; j < maxExclusive; j++) { - if (i != j) { - pairs.add(Tuple.of(i, j)); - } + return StreamSupport.stream(new PairSpliterator(maxExclusive), false); + } + + private static class PairSpliterator implements Spliterator> { + private final int maxExclusive; + + private int i = 0; + private int j = 1; + + public PairSpliterator(int maxExclusive) { + this.maxExclusive = maxExclusive; + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (j >= maxExclusive) { + return false; + } + action.accept(Tuple.of(i, j)); + j += 1; + if (j >= maxExclusive) { + i += 1; + j = i + 1; } + return true; + } + + @Override + public Spliterator> trySplit() { + return null; + } + + @Override + public long estimateSize() { + return (long) maxExclusive * (maxExclusive - 1) / 2; + } + + @Override + public int characteristics() { + return Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL | Spliterator.IMMUTABLE; } - return pairs.stream(); } + } diff --git a/engine/src/test/java/net/jqwik/engine/support/CombinatoricsTests.java b/engine/src/test/java/net/jqwik/engine/support/CombinatoricsTests.java index 282f68033..f5d15e89d 100644 --- a/engine/src/test/java/net/jqwik/engine/support/CombinatoricsTests.java +++ b/engine/src/test/java/net/jqwik/engine/support/CombinatoricsTests.java @@ -4,6 +4,7 @@ import java.util.stream.*; import net.jqwik.api.*; +import net.jqwik.api.Tuple.*; import net.jqwik.api.constraints.*; import static java.util.Arrays.*; @@ -27,6 +28,24 @@ void concatenatingIterables(@ForAll @Size(max = 10) List<@Size(max = 10) Lis assertThat(iterator.hasNext()).isFalse(); } + @Example + void distinctPairs() { + assertThat(Combinatorics.distinctPairs(0)).isEmpty(); + assertThat(Combinatorics.distinctPairs(1)).isEmpty(); + assertThat(Combinatorics.distinctPairs(2)).containsExactly( + Tuple.of(0, 1) + ); + assertThat(Combinatorics.distinctPairs(3)).containsExactly( + Tuple.of(0, 1), + Tuple.of(0, 2), + Tuple.of(1, 2) + ); + // (n * n-1) / 2: + assertThat(Combinatorics.distinctPairs(10)).hasSize(45); + assertThat(Combinatorics.distinctPairs(100)).hasSize(4950); + assertThat(Combinatorics.distinctPairs(1000)).hasSize(499500); + } + @Group @Label("listCombinations") class CombineList {