Skip to content

Commit

Permalink
Added tuple generator to ms-util
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnhatch committed Feb 2, 2024
1 parent 016daf0 commit 02d068b
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 0 deletions.
98 changes: 98 additions & 0 deletions src/main/java/util/combinatorics/TupleGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package util.combinatorics;

import java.util.ArrayList;
import java.util.List;

/**
* Utility class for generating tuples from a fixed set of finite ranges of the
* form [0,n). Uses a standard builder pattern for construction.
*/
public final class TupleGenerator {
private final int[] moduli;
private final int[] dimensions;
private final int size;

private TupleGenerator(List<Integer> dimensionSizes) {
dimensions = new int[dimensionSizes.size()];
moduli = new int[dimensionSizes.size()];

int count = 1;
for (int i = 0; i < dimensionSizes.size(); i++) {
int dimSize = dimensionSizes.get(i);
dimensions[i] = dimSize;
moduli[i] = count;
count *= dimSize;
}
size = count;
}

/**
* Returns the number of dimensions in this {@link TupleGenerator}.
*/
public int dimensions() {
return dimensions.length;
}

/**
* Returns the number of tuples that can be generated by this {@link TupleGenerator}
*/
public int size() {
return size;
}

/**
* Fills the given tuple with the values that correspond to the given index.
*
* @throws IndexOutOfBoundsException
* <ul>
* <li>if index &lt; 0</li>
* <li>if index &gt;= size()</li>
* </ul>
* @throws IllegalArgumentException
* <ul>
* <li>if the tuple is null</li>
* <li>if the tuple's length is not equal to
* dimensions()</li>
* </ul>
*/
public void fillTuple(int index, int[] tuple) {
if ((index < 0) || (index >= size)) {
throw new IndexOutOfBoundsException("index out of bounds");
}

if (tuple == null) {
throw new IllegalArgumentException("null array");
}

if (tuple.length != dimensions.length) {
throw new IllegalArgumentException("wrong number of dimensions");
}

for (int i = 0; i < dimensions.length; i++) {
tuple[i] = (index / moduli[i]) % dimensions[i];
}
}

public static Builder builder() {
return new Builder();
}

public static class Builder {
private List<Integer> dimensionSizes = new ArrayList<>();

private Builder() {
}

public TupleGenerator build() {
return new TupleGenerator(dimensionSizes);
}

public Builder addDimension(int dimensionSize) {
if (dimensionSize <= 0) {
throw new IllegalArgumentException("Non positive dimension size");
}
dimensionSizes.add(dimensionSize);
return this;
}
}
}
156 changes: 156 additions & 0 deletions src/test/java/util/combinatorics/AT_TupleGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package util.combinatorics;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.math3.random.RandomGenerator;
import org.junit.jupiter.api.Test;

import util.annotations.UnitTestMethod;
import util.random.RandomGeneratorProvider;

public class AT_TupleGenerator {

/**
* Tests {@link TupleGenerator#size()}
*/
@Test
@UnitTestMethod(target = TupleGenerator.class, name = "size", args = {})
public void testSize() {

RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7820715406750309229L);
for (int i = 0; i < 100; i++) {
TupleGenerator.Builder builder = TupleGenerator.builder();
int dimensionCount = randomGenerator.nextInt(4) + 1;
int expectedSize = 1;
for (int j = 0; j < dimensionCount; j++) {
int dimSize = randomGenerator.nextInt(10) + 1;
expectedSize *= dimSize;
builder.addDimension(dimSize);
}
int actualSize = builder.build().size();
assertEquals(expectedSize, actualSize);
}
}

/**
* Tests {@link TupleGenerator#builder()}
*/
@Test
@UnitTestMethod(target = TupleGenerator.class, name = "builder", args = {})
public void testBuilder() {
// covered by other tests
}

/**
* Tests {@link TupleGenerator#dimensions()}
*/
@Test
@UnitTestMethod(target = TupleGenerator.class, name = "dimensions", args = {})
public void testDimensions() {

RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7661626069466374878L);
for (int i = 0; i < 100; i++) {
TupleGenerator.Builder builder = TupleGenerator.builder();
int dimensionCount = randomGenerator.nextInt(4) + 1;
for (int j = 0; j < dimensionCount; j++) {
int dimSize = randomGenerator.nextInt(10) + 1;
builder.addDimension(dimSize);
}
int actualDimensionCount = builder.build().dimensions();
assertEquals(dimensionCount, actualDimensionCount);
}
}

/**
* Tests {@link TupleGenerator#fillTuple(int, int[])}
*/
@Test
@UnitTestMethod(target = TupleGenerator.class, name = "fillTuple", args = { int.class, int[].class })
public void testFillTuple() {

TupleGenerator tupleGenerator = TupleGenerator.builder().addDimension(2).addDimension(3).addDimension(5).build();

int[] tuple = new int[tupleGenerator.dimensions()];

List<int[]> expectedArrays = new ArrayList<>();

expectedArrays.add(new int[] { 0, 0, 0 });
expectedArrays.add(new int[] { 1, 0, 0 });
expectedArrays.add(new int[] { 0, 1, 0 });
expectedArrays.add(new int[] { 1, 1, 0 });
expectedArrays.add(new int[] { 0, 2, 0 });
expectedArrays.add(new int[] { 1, 2, 0 });
expectedArrays.add(new int[] { 0, 0, 1 });
expectedArrays.add(new int[] { 1, 0, 1 });
expectedArrays.add(new int[] { 0, 1, 1 });
expectedArrays.add(new int[] { 1, 1, 1 });
expectedArrays.add(new int[] { 0, 2, 1 });
expectedArrays.add(new int[] { 1, 2, 1 });
expectedArrays.add(new int[] { 0, 0, 2 });
expectedArrays.add(new int[] { 1, 0, 2 });
expectedArrays.add(new int[] { 0, 1, 2 });
expectedArrays.add(new int[] { 1, 1, 2 });
expectedArrays.add(new int[] { 0, 2, 2 });
expectedArrays.add(new int[] { 1, 2, 2 });
expectedArrays.add(new int[] { 0, 0, 3 });
expectedArrays.add(new int[] { 1, 0, 3 });
expectedArrays.add(new int[] { 0, 1, 3 });
expectedArrays.add(new int[] { 1, 1, 3 });
expectedArrays.add(new int[] { 0, 2, 3 });
expectedArrays.add(new int[] { 1, 2, 3 });
expectedArrays.add(new int[] { 0, 0, 4 });
expectedArrays.add(new int[] { 1, 0, 4 });
expectedArrays.add(new int[] { 0, 1, 4 });
expectedArrays.add(new int[] { 1, 1, 4 });
expectedArrays.add(new int[] { 0, 2, 4 });
expectedArrays.add(new int[] { 1, 2, 4 });

for (int i = 0; i < tupleGenerator.size(); i++) {
tupleGenerator.fillTuple(i, tuple);
assertTrue(Arrays.equals(expectedArrays.get(i), tuple));
}
/**
* precondition tests
*/
assertThrows(IndexOutOfBoundsException.class, () -> tupleGenerator.fillTuple(-2, tuple));
assertThrows(IndexOutOfBoundsException.class, () -> tupleGenerator.fillTuple(-1, tuple));
assertThrows(IndexOutOfBoundsException.class, () -> tupleGenerator.fillTuple(tupleGenerator.size(), tuple));
assertThrows(IndexOutOfBoundsException.class, () -> tupleGenerator.fillTuple(tupleGenerator.size() + 1, tuple));
assertThrows(IllegalArgumentException.class, () -> tupleGenerator.fillTuple(0, null));
assertThrows(IllegalArgumentException.class, () -> tupleGenerator.fillTuple(0, new int[tupleGenerator.dimensions() - 1]));
assertThrows(IllegalArgumentException.class, () -> tupleGenerator.fillTuple(0, new int[tupleGenerator.dimensions() + 1]));

}

@Test
@UnitTestMethod(target = TupleGenerator.Builder.class, name = "build", args = {})
public void testBuild() {
TupleGenerator.Builder builder = TupleGenerator.builder();
TupleGenerator tupleGenerator = builder.build();

assertNotNull(tupleGenerator);
}

@Test
@UnitTestMethod(target = TupleGenerator.Builder.class, name = "addDimension", args = { int.class })
public void testAddDimension() {
RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1967914502607382607L);
for (int i = 0; i < 100; i++) {
TupleGenerator.Builder builder = TupleGenerator.builder();
int dimensionCount = randomGenerator.nextInt(4) + 1;
for (int j = 0; j < dimensionCount; j++) {
int dimSize = randomGenerator.nextInt(10) + 1;
builder.addDimension(dimSize);
}
int actualDimensionCount = builder.build().dimensions();
assertEquals(dimensionCount, actualDimensionCount);
}
}
}

0 comments on commit 02d068b

Please sign in to comment.