From 02d068baab7039c506a8d05e533a68d81e26064c Mon Sep 17 00:00:00 2001 From: shawnhatch Date: Fri, 2 Feb 2024 08:44:20 -0500 Subject: [PATCH] Added tuple generator to ms-util --- .../util/combinatorics/TupleGenerator.java | 98 +++++++++++ .../util/combinatorics/AT_TupleGenerator.java | 156 ++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 src/main/java/util/combinatorics/TupleGenerator.java create mode 100644 src/test/java/util/combinatorics/AT_TupleGenerator.java diff --git a/src/main/java/util/combinatorics/TupleGenerator.java b/src/main/java/util/combinatorics/TupleGenerator.java new file mode 100644 index 0000000..476683a --- /dev/null +++ b/src/main/java/util/combinatorics/TupleGenerator.java @@ -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 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 + * + * @throws IllegalArgumentException + * + */ + 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 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; + } + } +} diff --git a/src/test/java/util/combinatorics/AT_TupleGenerator.java b/src/test/java/util/combinatorics/AT_TupleGenerator.java new file mode 100644 index 0000000..09fb146 --- /dev/null +++ b/src/test/java/util/combinatorics/AT_TupleGenerator.java @@ -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 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); + } + } +}