Skip to content

Commit c08231b

Browse files
authored
Descrease amount of calls to nextInt for numerify (#1545)
1 parent 3886e44 commit c08231b

File tree

4 files changed

+80
-24
lines changed

4 files changed

+80
-24
lines changed

src/main/java/net/datafaker/service/FakeValuesService.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,30 +373,35 @@ public String bothify(String input, FakerContext context, boolean isUpper) {
373373

374374
private String bothify(String input, FakerContext context, boolean isUpper, boolean numerify, boolean letterify) {
375375
final int baseChar = isUpper ? 'A' : 'a';
376+
boolean changed = false;
376377
final char[] res = input.toCharArray();
378+
RandomService randomService = context.getRandomService();
377379
for (int i = 0; i < res.length; i++) {
378380
switch (res[i]) {
379381
case '#' -> {
380382
if (numerify) {
381-
res[i] = DIGITS[context.getRandomService().nextInt(10)];
383+
changed = true;
384+
i += GenerationUtils.generateAndSetNumber(i, res, res[i], randomService) - 1;
382385
}
383386
}
384387
case 'Ø' -> {
385388
if (numerify) {
386-
res[i] = DIGITS[context.getRandomService().nextInt(1, 9)];
389+
changed = true;
390+
res[i] = DIGITS[randomService.nextInt(1, 9)];
387391
}
388392
}
389393
case '?' -> {
390394
if (letterify) {
391-
res[i] = (char) (baseChar + context.getRandomService().nextInt(26)); // a-z
395+
changed = true;
396+
res[i] = (char) (baseChar + randomService.nextInt(26)); // a-z
392397
}
393398
}
394399
default -> {
395400
}
396401
}
397402
}
398403

399-
return String.valueOf(res);
404+
return changed ? String.valueOf(res) : input;
400405
}
401406

402407
/**
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package net.datafaker.service;
2+
3+
public class GenerationUtils {
4+
private static final char[] DIGITS = "0123456789".toCharArray();
5+
6+
// Based on Integer.MAX_VALUE the max value is 1_000_000_000
7+
// assuming that every digit should have equal chance.
8+
private static final int INT_LIMIT_DIGITS = 9;
9+
private static final int[] TENS = initTens();
10+
11+
private static int generateNumber(RandomService randomService, int amountOfDigits) {
12+
if (amountOfDigits > INT_LIMIT_DIGITS || amountOfDigits < 1) {
13+
// should never happen
14+
// in case it happened most probably there is a bug in a method invoking this
15+
throw new IllegalArgumentException("Invalid amount of digits: " + amountOfDigits);
16+
}
17+
return randomService.nextInt(TENS[amountOfDigits + 1]);
18+
}
19+
20+
static int generateAndSetNumber(int position, char[] target, char symbol, RandomService randomService) {
21+
int symbolCounter = 0;
22+
int generated = 0;
23+
do {
24+
symbolCounter++;
25+
26+
if (symbolCounter - generated == INT_LIMIT_DIGITS) {
27+
final int r = generateNumber(randomService, INT_LIMIT_DIGITS);
28+
insertNumber(r, INT_LIMIT_DIGITS, target, position + generated);
29+
generated += INT_LIMIT_DIGITS;
30+
}
31+
} while (position + symbolCounter < target.length && target[position + symbolCounter] == symbol);
32+
33+
final int diff = symbolCounter - generated;
34+
if (diff > 0) {
35+
final int r = generateNumber(randomService, diff);
36+
insertNumber(r, diff, target, position + generated);
37+
}
38+
return symbolCounter;
39+
}
40+
41+
private static int[] initTens() {
42+
int[] tens = new int[INT_LIMIT_DIGITS + 2];
43+
tens[0] = 1;
44+
for (int i = 1; i < tens.length; i++) {
45+
tens[i] = tens[i-1] * 10;
46+
}
47+
return tens;
48+
}
49+
50+
private static void insertNumber(int number, int amountOfDigits, char[] target, int offset) {
51+
for (int k = 0; k < amountOfDigits; k++) {
52+
target[offset + k] = DIGITS[(number / TENS[k] % 10)];
53+
}
54+
}
55+
}

src/test/java/net/datafaker/FakerTest.java

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.junit.jupiter.api.Test;
88
import org.junit.jupiter.api.Timeout;
99
import org.junit.jupiter.params.ParameterizedTest;
10+
import org.junit.jupiter.params.provider.CsvSource;
1011
import org.junit.jupiter.params.provider.ValueSource;
1112
import org.reflections.Reflections;
1213

@@ -76,24 +77,19 @@ void letterifyShouldLeaveNonSpecialCharactersAlone() {
7677
assertThat(faker.bothify("ABC????DEF")).matches("ABC\\w{4}DEF");
7778
}
7879

79-
@Test
80-
void numerifyShouldGenerateNumbers() {
81-
assertThat(faker.numerify("####")).matches("\\d{4}");
82-
}
83-
84-
@RepeatedTest(25)
85-
void numerifyShouldGenerateNumbersNotStartingWithZero() {
86-
assertThat(faker.numerify("Ø###")).matches("[1-9]\\d{3}");
87-
}
88-
89-
@RepeatedTest(25)
90-
void numerifyShouldGenerateNonZeroNumbers() {
91-
assertThat(faker.numerify("ØØ")).matches("[1-9]{2}");
92-
}
93-
94-
@Test
95-
void numerifyShouldLeaveNonSpecialCharactersAlone() {
96-
assertThat(faker.numerify("####123")).matches("\\d{4}123");
80+
@ParameterizedTest
81+
@CsvSource(delimiter = ';',
82+
value = {
83+
"Ø###;[1-9]\\d{3}",
84+
"ØØ;[1-9]{2}",
85+
"####123;\\d{4}123",
86+
"####;\\d{4}",
87+
"#############;\\d{13}",
88+
"################;\\d{16}",
89+
"####################;\\d{20}"
90+
} )
91+
void numerifyTest(String input, String expected) {
92+
assertThat(faker.numerify(input)).matches(expected);
9793
}
9894

9995
@Test

src/test/java/net/datafaker/annotations/FakeAnnotationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ void shouldGenerateEntityFromJavaRecordWithComplexSchemaWhenClassTemplateWithout
7474

7575
assertThat(person).isNotNull();
7676
assertThat(person.name()).isEqualTo("Aztar Ivy");
77-
assertThat(person.address()).isEqualTo("Am Buttermarkt 46b, Dannerheim, BE 32422");
78-
assertThat(person.color()).isEqualTo("rot");
77+
assertThat(person.address()).isEqualTo("Am Buttermarkt 43b, Furkanheim, BE 36219");
78+
assertThat(person.color()).isEqualTo("blau");
7979
}
8080

8181
@Test

0 commit comments

Comments
 (0)