Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement excludingRange and withRange functionality for Strings #72

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions core/src/main/java/org/quicktheories/generators/Strings.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
package org.quicktheories.generators;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;

import org.quicktheories.api.Pair;
import org.quicktheories.core.Gen;

final class Strings {

static Gen<String> fromRanges(List<Pair<Integer, Integer>> ranges, int minLength, int maxLength) {
ranges = new ArrayList<>(ranges);
// Sorting by range size seems like it ought to reduce the frequency with which we have to bump the percentage up
// from zero below
ranges.sort(Comparator.comparing(r -> r._2 - r._1));
Pair<Integer, Integer> first = ranges.get(0);
Gen<Integer> gen = CodePoints.codePoints(first._1, first._2);
int total = 1 + ranges.get(0)._2 - ranges.get(0)._1;
for (int i = 1; i < ranges.size(); i++) {
Pair<Integer, Integer> next = ranges.get(i);
Gen<Integer> gen2 = CodePoints.codePoints(next._1, next._2);
int nextSize = 1 + next._2 - next._1;
int weight = (int) (100 * ((float) total / (total + nextSize)));
if (weight == 0) {
weight = 1;
}
weight = 100 - weight;
total = total + nextSize;
gen = gen.mix(gen2, weight);
}
return Generate.intArrays(Generate.range(minLength, maxLength)
, gen)
.map(is -> new String(is, 0, is.length)).map(reduceToSize(maxLength));
}

static Gen<String> boundedNumericStrings(int startInclusive,
int endInclusive) {
return Generate.range(startInclusive, endInclusive)
Expand Down
63 changes: 53 additions & 10 deletions core/src/main/java/org/quicktheories/generators/StringsDSL.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package org.quicktheories.generators;

import org.quicktheories.api.Pair;
import org.quicktheories.core.Gen;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
* A Class for creating String Sources that will produce Strings composed of
* code points within the specified domain.
Expand Down Expand Up @@ -97,15 +103,55 @@ public StringGeneratorBuilder betweenCodePoints(int minInclusive, int maxInclusi

public static class StringGeneratorBuilder {

private final int minCodePoint;
private final int maxCodePoint;
private final List<Pair<Integer, Integer>> ranges;

private StringGeneratorBuilder(int minCodePoint, int maxCodePoint) {
this.minCodePoint = minCodePoint;
this.maxCodePoint = maxCodePoint;
this.ranges = Collections.singletonList(Pair.of(minCodePoint, maxCodePoint));
}

/**
private StringGeneratorBuilder(List<Pair<Integer, Integer>> ranges) {
this.ranges = ranges;
}

public StringGeneratorBuilder excludingCodePoint(int codePoint) {
return excludingRange(codePoint, codePoint);
}

public StringGeneratorBuilder excludingRange(int minCodePoint, int maxCodePoint) {
return null;
}

public StringGeneratorBuilder withCodePoint(int codePoint) {
return withRange(codePoint, codePoint);
}

public StringGeneratorBuilder withRange(int minCodePoint, int maxCodePoint) {
List<Pair<Integer, Integer>> newRanges = new ArrayList<>();
Iterator<Pair<Integer, Integer>> it = ranges.iterator();
boolean added = false;
while (it.hasNext()) {
Pair<Integer, Integer> next = it.next();
if (next._1 > maxCodePoint) {
newRanges.add(Pair.of(minCodePoint, maxCodePoint));
added = true;
} else if (next._2 < minCodePoint) {
newRanges.add(next);
}
// If ranges overlap but neither contains the other, we can just combine them
else if (next._2 > maxCodePoint) {
newRanges.add(Pair.of(minCodePoint, next._2));
added = true;
}
// Otherwise, min/max dominates next, and we can skip next
}
if (!added) {
newRanges.add(Pair.of(minCodePoint, maxCodePoint));
}
return new StringGeneratorBuilder(newRanges);
}


/**
* Generates Strings of a fixed number of code points.
*
* @param codePoints
Expand All @@ -116,8 +162,7 @@ public Gen<String> ofFixedNumberOfCodePoints(int codePoints) {
ArgumentAssertions.checkArguments(codePoints >= 0,
"The number of codepoints cannot be negative; %s is not an accepted argument",
codePoints);
return Strings.withCodePoints(minCodePoint,
maxCodePoint, Generate.constant(codePoints));
return Strings.fromRanges(ranges, codePoints, codePoints);
}

/**
Expand Down Expand Up @@ -148,10 +193,8 @@ public Gen<String> ofLengthBetween(int minLength, int maxLength) {
ArgumentAssertions.checkArguments(minLength >= 0,
"The length of a String cannot be negative; %s is not an accepted argument",
minLength);
return Strings.ofBoundedLengthStrings(minCodePoint, maxCodePoint,
minLength, maxLength);
return Strings.fromRanges(ranges, minLength, maxLength);
}

}

}
14 changes: 12 additions & 2 deletions core/src/test/java/org/quicktheories/generators/StringsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

import org.junit.Test;
import org.quicktheories.core.Gen;
import org.quicktheories.generators.Generate;
import org.quicktheories.generators.Strings;

public class StringsTest {

Expand Down Expand Up @@ -43,4 +41,16 @@ public void shouldShrinkFixedCodepointStringsTowardsExclaimationMarks() {
assertThatGenerator(testee).shrinksTowards("!!!");
}

@Test
public void shouldIncludeLettersAndNumbersForAlphaNumeric() {
Gen<String> testee = new StringsDSL().betweenCodePoints(48, 57).withRange(65, 91).ofLength(1);
assertThatGenerator(testee).generatesAllOf("0", "A");
}

@Test
public void shouldNotIgnoreSmallRange() {
Gen<String> testee = new StringsDSL().betweenCodePoints(48, 48).withRange(256, 0xD7FB).ofLength(1);
assertThatGenerator(testee).generatesAllOf("0");
}

}