getGroups() {
+ return workUnits[workUnitIndex].getGroups();
+ }
+
+}
diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/StackableItemGroupPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/StackableItemGroupPermutationRotationIterator.java
new file mode 100644
index 00000000..35836074
--- /dev/null
+++ b/core/src/main/java/com/github/skjolber/packing/iterator/StackableItemGroupPermutationRotationIterator.java
@@ -0,0 +1,44 @@
+package com.github.skjolber.packing.iterator;
+
+import java.util.List;
+
+import com.github.skjolber.packing.api.StackValue;
+
+/**
+ *
+ * Rotation and permutations built into the same interface. Minimizes the number of
+ * rotations.
+ *
+ * The maximum number of combinations is n! * 6^n, however after accounting for
+ * bounds and sides with equal lengths the number can be a lot lower (and this
+ * number can be obtained before starting the calculation).
+ *
+ * Note that permutations are for the boxes which actually fit within this container.
+ *
+ *
+ * Assumes a do-while approach:
+ *
+ *
+ * {@code
+ * do {
+ * do {
+ * for (int i = 0; i < n; i++) {
+ * PermutationRotation box = instance.get(i);
+ * // .. your code here
+ * }
+ * } while (instance.nextRotation() != -1);
+ * } while (instance.nextPermutation() != -1);
+ *
+ * }
+ *
+ *
+ * @see next-lexicographical-permutation-algorithm
+ */
+
+public interface StackableItemGroupPermutationRotationIterator extends StackableItemPermutationRotationIterator {
+
+ List getGroups();
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/StackableItemPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/StackableItemPermutationRotationIterator.java
new file mode 100644
index 00000000..339e3d79
--- /dev/null
+++ b/core/src/main/java/com/github/skjolber/packing/iterator/StackableItemPermutationRotationIterator.java
@@ -0,0 +1,140 @@
+package com.github.skjolber.packing.iterator;
+
+import java.util.List;
+
+import com.github.skjolber.packing.api.StackValue;
+
+/**
+ *
+ * Rotation and permutations built into the same interface. Minimizes the number of
+ * rotations.
+ *
+ * The maximum number of combinations is n! * 6^n, however after accounting for
+ * bounds and sides with equal lengths the number can be a lot lower (and this
+ * number can be obtained before starting the calculation).
+ *
+ * Note that permutations are for the boxes which actually fit within this container.
+ *
+ *
+ * Assumes a do-while approach:
+ *
+ *
+ * {@code
+ * do {
+ * do {
+ * for (int i = 0; i < n; i++) {
+ * PermutationRotation box = instance.get(i);
+ * // .. your code here
+ * }
+ * } while (instance.nextRotation() != -1);
+ * } while (instance.nextPermutation() != -1);
+ *
+ * }
+ *
+ *
+ * @see next-lexicographical-permutation-algorithm
+ */
+
+public interface StackableItemPermutationRotationIterator {
+
+ /**
+ *
+ * Get current length
+ *
+ * @return current length of permutations array
+ */
+
+ int length();
+
+ StackValue getStackValue(int index);
+
+ /**
+ * Get current state
+ *
+ * @return current state
+ */
+
+ PermutationRotationState getState();
+
+ /**
+ *
+ * Get permutations + rotations for a state
+ *
+ * @param state previously saved state
+ * @param length number of items
+ * @return current permutations + rotations
+ */
+
+ List get(PermutationRotationState state, int length);
+
+ long getMinStackableVolume(int index);
+
+ long[] getMinStackableVolume();
+
+ int getMinStackableAreaIndex(int i);
+
+ /**
+ * Get current permutations
+ *
+ * @return current permutations array
+ */
+
+ int[] getPermutations();
+
+ long countRotations();
+
+ long countPermutations();
+
+ // write access methods
+
+
+ /**
+ * Next rotation.
+ *
+ * @return change index, or -1 if none
+ */
+
+ int nextRotation();
+
+ /**
+ * Next rotation. Returns the index of the lowest element which as affected.
+ *
+ * @param maxIndex skip ahead so that rotation affects the argument at index or lower.
+ * @return change index, or -1 if none
+ */
+
+ int nextRotation(int maxIndex);
+
+ /**
+ * Next permutation.
+ *
+ * @return change index, or -1 if none
+ */
+
+ int nextPermutation();
+
+ /**
+ * Next permutation. Returns the index of the lowest element which as affected.
+ *
+ * @param maxIndex skip ahead so that permutation affects the argument at index or lower.
+ * @return change index, or -1 if none
+ */
+
+ int nextPermutation(int maxIndex);
+
+
+ /**
+ * Remove permutations, if present.
+ *
+ * @param removed list of permutation indexes to remove
+ */
+
+ void removePermutations(List removed);
+
+ void removePermutations(int count);
+
+ IndexedStackableItem[] getStackableItems();
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupPermutationRotationIteratorTest.java b/core/src/test/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupPermutationRotationIteratorTest.java
new file mode 100644
index 00000000..066b56b8
--- /dev/null
+++ b/core/src/test/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupPermutationRotationIteratorTest.java
@@ -0,0 +1,326 @@
+package com.github.skjolber.packing.iterator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.github.skjolber.packing.api.Box;
+import com.github.skjolber.packing.api.Dimension;
+import com.github.skjolber.packing.api.StackableItem;
+import com.github.skjolber.packing.api.StackableItemGroup;
+
+@SuppressWarnings("unchecked")
+public abstract class AbstractStackableItemGroupPermutationRotationIteratorTest{
+
+ public abstract T newBuilder();
+
+ @Test
+ void testPermutations() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .build();
+
+ int count = 0;
+ do {
+ count++;
+ System.out.println(Arrays.toString(rotator.getPermutations()));
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((3 * 2 * 1) * (3 * 2 * 1), count);
+
+ assertEquals(count, rotator.countPermutations());
+ }
+
+ @Test
+ void testPermutationsRepeatedItems() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("6").withWeight(1).build(), 2));
+ groups.add(new StackableItemGroup("2", products2));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .build();
+
+ int count = 0;
+ do {
+ count++;
+ System.out.println(Arrays.toString(rotator.getPermutations()));
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((3 * 2 * 1) * (5 * 4 * 3 * 2 * 1) / 2, count);
+
+ assertEquals(count, rotator.countPermutations());
+ }
+
+ @Test
+ void testRemovePermutations() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .build();
+
+ rotator.removePermutations(1);
+
+ int count = 0;
+ do {
+ count++;
+ System.out.println(Arrays.toString(rotator.getPermutations()));
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((2 * 1) * (3 * 2 * 1), count);
+
+ assertEquals(count, rotator.countPermutations());
+ }
+
+ @Test
+ void testRemoveWholeGroup() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .build();
+
+ rotator.removePermutations(1);
+ // remove the rest of the first group
+ rotator.removePermutations(2);
+
+ int count = 0;
+ do {
+ count++;
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals(3 * 2 * 1, rotator.countPermutations());
+ assertEquals(count, rotator.countPermutations());
+ }
+
+
+ @Test
+ void testNextPermutationMaxIndexGroup1() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("6").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("7").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("8").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ StackableItemPermutationRotationIterator iterator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .build();
+
+ int[] before = iterator.getPermutations();
+
+ int maxIndex = 3;
+
+ System.out.println(Arrays.toString(iterator.getPermutations()));
+
+ iterator.nextPermutation(maxIndex);
+
+ System.out.println(Arrays.toString(iterator.getPermutations()));
+
+ int[] after = iterator.getPermutations();
+ for(int i = 0; i < maxIndex; i++) {
+ assertEquals(before[i], after[i]);
+ }
+
+ assertNotEquals(before[maxIndex], after[maxIndex]);
+
+ // group 2 should be reset
+ for(int i = products1.size(); i < after.length - 1; i++) {
+ assertTrue(before[i] <= after[i + 1]);
+ }
+ }
+
+ @Test
+ void testNextPermutationMaxIndexGroup2() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("6").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("7").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("8").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ StackableItemPermutationRotationIterator iterator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .build();
+
+ int[] before = iterator.getPermutations();
+
+ System.out.println(Arrays.toString(before));
+
+ int maxIndex = 6;
+
+ iterator.nextPermutation(maxIndex);
+
+ int[] after = iterator.getPermutations();
+
+ System.out.println(Arrays.toString(iterator.getPermutations()));
+
+ for(int i = 0; i < maxIndex; i++) {
+ assertEquals(before[i], after[i]);
+ }
+
+ assertNotEquals(before[maxIndex], after[maxIndex]);
+
+ // group 1 should not be touched
+ for(int i = 0; i < products1.size(); i++) {
+ assertEquals(before[i], after[i]);
+ }
+ }
+
+ @Test
+ void testNextPermutationMaxIndexTransitionGroup() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("6").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("7").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("8").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ StackableItemPermutationRotationIterator iterator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .build();
+
+ // go to the last permuation of the second group
+ for(int i = 0; i < 4 * 3 * 2 * 1 - 1; i++) {
+ iterator.nextPermutation();
+ }
+
+ int[] before = iterator.getPermutations();
+
+ System.out.println(Arrays.toString(iterator.getPermutations()));
+
+ int maxIndex = 6;
+
+ int index = iterator.nextPermutation(maxIndex);
+
+ assertTrue(index < products1.size());
+
+ int[] after = iterator.getPermutations();
+
+ System.out.println(Arrays.toString(after));
+
+ for(int i = 0; i < index; i++) {
+ assertEquals(before[i], after[i]);
+ }
+
+ assertNotEquals(before[index], after[index]);
+ }
+}
diff --git a/core/src/test/java/com/github/skjolber/packing/iterator/AbstractStackableItemPermutationRotationIteratorTest.java b/core/src/test/java/com/github/skjolber/packing/iterator/AbstractStackableItemPermutationRotationIteratorTest.java
new file mode 100644
index 00000000..9984d6c5
--- /dev/null
+++ b/core/src/test/java/com/github/skjolber/packing/iterator/AbstractStackableItemPermutationRotationIteratorTest.java
@@ -0,0 +1,514 @@
+package com.github.skjolber.packing.iterator;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.github.skjolber.packing.api.Box;
+import com.github.skjolber.packing.api.Dimension;
+import com.github.skjolber.packing.api.StackValue;
+import com.github.skjolber.packing.api.StackableItem;
+
+public abstract class AbstractStackableItemPermutationRotationIteratorTest {
+
+ protected static void assertMinStackableVolumeValid(StackableItemPermutationRotationIterator iterator) {
+ for (int i = 0; i < iterator.length(); i++) {
+ long calculatedMinStackableVolume = getMinStackableVolume(iterator, i);
+ long cachedMinStackableVolume = iterator.getMinStackableVolume(i);
+
+ assertEquals(calculatedMinStackableVolume, cachedMinStackableVolume, "Calculated " + calculatedMinStackableVolume + ", got " + cachedMinStackableVolume);
+ }
+ }
+
+ protected static long getMinStackableVolume(StackableItemPermutationRotationIterator iterator, int offset) {
+ long minVolume = Long.MAX_VALUE;
+ for (int i = offset; i < iterator.length(); i++) {
+ StackValue stackValue = iterator.getStackValue(i);
+ long volume = stackValue.getVolume();
+ if(volume < minVolume) {
+ minVolume = volume;
+ }
+ }
+ return minVolume;
+ }
+
+ public abstract T newBuilder();
+
+ @Test
+ void testRotationCount() {
+ for (int i = 1; i <= 8; i++) {
+ Dimension container = new Dimension(null, 3 * (i + 1), 3, 1);
+
+ List products1 = new ArrayList<>();
+
+ for (int k = 0; k < i; k++) {
+ Box box = Box.newBuilder().withSize(3, 1, 1).withRotate3D().withDescription(Integer.toString(k)).withWeight(1).build();
+
+ StackableItem item = new StackableItem(box);
+
+ products1.add(item);
+ }
+
+ StackableItemPermutationRotationIterator rotator =
+ newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products1)
+ .withMaxLoadWeight(products1.size())
+ .build();
+
+ long count = rotator.countRotations();
+
+ long rotate = 0;
+ do {
+ rotate++;
+ } while (rotator.nextRotation() != -1);
+
+ assertEquals(count, rotate, "Rotations for " + i);
+ }
+ }
+
+ @Test
+ void testUnconstrainedRotationCount() {
+ Dimension container = new Dimension(null, 3, 3, 3);
+
+ List products = new ArrayList<>();
+
+ Box box = Box.newBuilder().withSize(1, 2, 3).withRotate3D().withDescription("0").withWeight(1).build();
+ products.add(new StackableItem(box));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(6, rotator.countRotations());
+ }
+
+ @Test
+ void testNumberOfConstrainedRotations() {
+ Dimension container = new Dimension(null, 1, 2, 3);
+
+ List products = new ArrayList<>();
+
+ Box box = Box.newBuilder().withRotate3D().withSize(1, 2, 3).withDescription("0").withWeight(1).build();
+
+ products.add(new StackableItem(box));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(1, rotator.countRotations());
+ }
+
+ @Test
+ void testNumberOfConstrainedRotationsWithOutOfScopeBox() {
+ Dimension container = new Dimension(null, 4, 4, 4);
+
+ List products = new ArrayList<>();
+
+ Box box1 = Box.newBuilder().withRotate3D().withSize(1, 2, 3).withDescription("0").withWeight(1).build();
+ Box box2 = Box.newBuilder().withRotate3D().withSize(5, 2, 2).withDescription("0").withWeight(1).build(); // too big
+
+ products.add(new StackableItem(box1));
+ products.add(new StackableItem(box2));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(6, rotator.countRotations());
+ }
+
+
+ @Test
+ void testNumberOfRotationsForSquare2D() {
+ Dimension container = new Dimension(null, 3, 3, 3);
+
+ List products = new ArrayList<>();
+
+ Box box = Box.newBuilder().withSize(3, 1, 1).withRotate2D().withDescription("0").withWeight(1).build();
+ products.add(new StackableItem(box));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(2, rotator.countRotations());
+ }
+
+ @Test
+ void testNumberOfConstrainedRotationsForSquare2D() {
+ Dimension container = new Dimension(null, 3, 1, 1);
+
+ List products = new ArrayList<>();
+
+ Box box = Box.newBuilder().withSize(3, 1, 1).withRotate2D().withDescription("0").withWeight(1).build();
+ products.add(new StackableItem(box));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(1, rotator.countRotations());
+ }
+
+ @Test
+ void testNumberOfRotationsForSquare3D() {
+ Dimension container = new Dimension(null, 3, 3, 3);
+
+ List products = new ArrayList<>();
+
+ Box box = Box.newBuilder().withRotate3D().withSize(1, 1, 1).withDescription("0").withWeight(1).build();
+ products.add(new StackableItem(box));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(1, rotator.countRotations());
+ }
+
+ @Test
+ void testRotation() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(1, rotator.countRotations());
+
+ do {
+ // check order unchanged
+ for (int i = 0; i < products.size(); i++) {
+ assertEquals(Integer.toString(i), rotator.getStackValue(i).getStackable().getDescription());
+ }
+
+ // all rotations can fit
+ for (int i = 0; i < products.size(); i++) {
+ assertTrue(rotator.getStackValue(i).fitsInside3D(container));
+ }
+
+ assertMinStackableVolumeValid(rotator);
+ } while (rotator.nextRotation() != -1);
+
+ }
+
+ @Test
+ void testPermutations() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ int count = 0;
+ do {
+ assertMinStackableVolumeValid(rotator);
+
+ count++;
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals(5 * 4 * 3 * 2 * 1, count);
+ }
+
+ @Test
+ void testPermutationsForMaxIndex() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ int count = 0;
+ do {
+ count++;
+ } while (rotator.nextPermutation(rotator.length() - 1) != -1);
+
+ assertEquals(5 * 4 * 3 * 2 * 1, count);
+ }
+
+ @Test
+ void testPermutationsForMaxIndexInRightOrder() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 4).withDescription("1").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 5).withDescription("2").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 6).withDescription("3").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 7).withDescription("4").withWeight(1).build()));
+
+ StackableItemPermutationRotationIterator rotator1 = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ StackableItemPermutationRotationIterator rotator2 = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ int count = 0;
+ do {
+ assertMinStackableVolumeValid(rotator1);
+ assertMinStackableVolumeValid(rotator2);
+
+ int[] permutations1 = rotator1.getPermutations();
+ int[] permutations2 = rotator2.getPermutations();
+ assertArrayEquals(permutations1, permutations2);
+
+ count++;
+ } while (rotator1.nextPermutation(rotator1.length() - 1) != -1 && rotator2.nextPermutation() != -1);
+
+ assertEquals(5 * 4 * 3 * 2 * 1, count);
+ }
+
+ @Test
+ void testPermutationCorrectIndexReturned() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 4).withDescription("1").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 5).withDescription("2").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 6).withDescription("3").withWeight(1).build()));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ int count = 0;
+ do {
+ count++;
+
+ int[] permutations = cloneArray(rotator.getPermutations());
+
+ int length = rotator.nextPermutation();
+
+ if(length == -1) {
+ break;
+ }
+ assertThat(firstDiffIndex(permutations, rotator.getPermutations())).isEqualTo(length);
+
+ } while (true);
+
+ assertEquals(4 * 3 * 2 * 1, count);
+ }
+
+ public static int firstDiffIndex(int[] a, int[] b) {
+ for (int i = 0; i < a.length; i++) {
+ if(a[i] != b[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static int[] cloneArray(int[] permutations) {
+ int[] clone = new int[permutations.length];
+ System.arraycopy(permutations, 0, clone, 0, permutations.length);
+ return clone;
+ }
+
+ @Test
+ void testPermutationsWithMultipleBoxes() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build(), 2));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build(), 4));
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ int count = 0;
+ do {
+ count++;
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((6 * 5 * 4 * 3 * 2 * 1) / ((4 * 3 * 2 * 1) * (2 * 1)), count);
+ }
+
+ @Test
+ void testCounts() {
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(5, 10, 10).withDescription("0").withWeight(1).build(), 2));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(5, 10, 10).withDescription("1").withWeight(1).build(), 2));
+
+ int n = 4;
+
+ Dimension container = new Dimension(null, 5 * n, 10, 10);
+
+ StackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ int length = rotator.length();
+
+ assertEquals(4, length);
+
+ }
+
+ @Test
+ void testCountPermutations1() {
+ int n = 25;
+
+ Dimension container = new Dimension(null, 5 * n, 10, 10);
+
+ List products = new ArrayList<>();
+ for (int k = 0; k < n; k++) {
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(5, 10, 10).withWeight(1).build(), 1));
+ }
+
+ StackableItemPermutationRotationIterator iterator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(-1L, iterator.countPermutations());
+ }
+
+ @Test
+ void testCountPermutations2() {
+ int n = 25;
+
+ Dimension container = new Dimension(null, 5 * n, 10, 10);
+
+ List products = new ArrayList<>();
+ for (int k = 0; k < n; k++) {
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(5, 10, 10).withWeight(1).build(), 1));
+ }
+ StackableItemPermutationRotationIterator iterator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ assertEquals(-1L, iterator.countPermutations());
+ }
+
+ @Test
+ void testRemovePermutations1() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+
+ StackableItemPermutationRotationIterator iterator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ iterator.removePermutations(3);
+
+ int[] permutations = iterator.getPermutations();
+
+ assertEquals(permutations.length, 2);
+ assertEquals(3, permutations[0]);
+ assertEquals(4, permutations[1]);
+
+ int nextPermutation = iterator.nextPermutation();
+ assertEquals(0, nextPermutation);
+
+ // no more rotations
+ assertEquals(-1, iterator.nextPermutation());
+ }
+
+ @Test
+ void testRemovePermutations2() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+
+ StackableItemPermutationRotationIterator iterator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ List remove = new ArrayList<>();
+ remove.add(2);
+ remove.add(4);
+ iterator.removePermutations(remove);
+
+ int[] permutations = iterator.getPermutations();
+ assertEquals(0, permutations[0]);
+ assertEquals(1, permutations[1]);
+ assertEquals(3, permutations[2]);
+ }
+
+
+}
diff --git a/core/src/test/java/com/github/skjolber/packing/iterator/DefaultStackableItemGroupPermutationRotationIteratorTest.java b/core/src/test/java/com/github/skjolber/packing/iterator/DefaultStackableItemGroupPermutationRotationIteratorTest.java
new file mode 100644
index 00000000..976bcdb0
--- /dev/null
+++ b/core/src/test/java/com/github/skjolber/packing/iterator/DefaultStackableItemGroupPermutationRotationIteratorTest.java
@@ -0,0 +1,12 @@
+package com.github.skjolber.packing.iterator;
+
+import com.github.skjolber.packing.iterator.DefaultStackableItemGroupPermutationRotationIterator.Builder;
+
+public class DefaultStackableItemGroupPermutationRotationIteratorTest extends AbstractStackableItemGroupPermutationRotationIteratorTest {
+
+ @Override
+ public Builder newBuilder() {
+ return DefaultStackableItemGroupPermutationRotationIterator.newBuilder();
+ }
+
+}
diff --git a/core/src/test/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemGroupPermutationRotationIteratorTest.java b/core/src/test/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemGroupPermutationRotationIteratorTest.java
new file mode 100644
index 00000000..aad53a42
--- /dev/null
+++ b/core/src/test/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemGroupPermutationRotationIteratorTest.java
@@ -0,0 +1,175 @@
+package com.github.skjolber.packing.iterator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.github.skjolber.packing.api.Box;
+import com.github.skjolber.packing.api.Dimension;
+import com.github.skjolber.packing.api.StackableItem;
+import com.github.skjolber.packing.api.StackableItemGroup;
+import com.github.skjolber.packing.api.packager.StackableItems;
+import com.github.skjolber.packing.iterator.MutableIndexedStackableItemPermutationRotationIterator.Builder;
+import com.github.skjolber.packing.iterator.MutableIndexedStackableItemPermutationRotationIterator.DelegateBuilder;
+
+class MutableIndexedStackableItemGroupPermutationRotationIteratorTest extends AbstractStackableItemGroupPermutationRotationIteratorTest {
+
+ @Override
+ public MutableIndexedStackableItemGroupPermutationRotationIterator.DelegateBuilder newBuilder() {
+ return new MutableIndexedStackableItemGroupPermutationRotationIterator.DelegateBuilder(DefaultStackableItemGroupPermutationRotationIterator.newBuilder());
+ }
+
+ @Test
+ void testMutableRotationCount() {
+ for (int i = 1; i <= 8; i++) {
+ Dimension container = new Dimension(null, 3 * (i + 1), 3, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+
+ for (int k = 0; k < i; k++) {
+ Box box = Box.newBuilder().withSize(3, 1, 1).withRotate3D().withId(Integer.toString(k)).withWeight(1).build();
+
+ StackableItem item = new StackableItem(box);
+
+ products1.add(item);
+ }
+
+ groups.add(new StackableItemGroup("1", products1));
+
+ MutableIndexedStackableItemGroupPermutationRotationIterator rotator =
+ newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products1.size())
+ .build();
+
+ StackableItems items = rotator;
+
+ long unmodifiedRotationsCount = rotator.getIterator().countRotations();
+
+ long modifiedRotationsCount = rotator.countRotations();
+
+ assertTrue(unmodifiedRotationsCount >= modifiedRotationsCount);
+
+ long rotate = 0;
+ do {
+ // removing items do not affect the number of rotations
+ assertEquals(items.size(), products1.size());
+
+ items.remove(0, 1);
+ for(int k = 0; k < items.size(); k++) {
+ StackableItem item = items.get(k);
+ assertFalse(item.getStackable().getId().equals("0"));
+ }
+
+ rotate++;
+ } while (rotator.nextRotation() != -1);
+
+ assertEquals(unmodifiedRotationsCount, rotate);
+ }
+ }
+
+ @Test
+ void testMutablePermutationsWithMultipleBoxes() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withId("0").withWeight(1).build(), 2));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withId("1").withWeight(1).build(), 4));
+
+ List groups = new ArrayList<>();
+ groups.add(new StackableItemGroup("1", products));
+
+ MutableIndexedStackableItemGroupPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+
+ int count = 0;
+ do {
+ assertEquals(rotator.size(), products.size());
+
+ // removing items do not affect the number of permutations
+ rotator.remove(0, 1);
+
+ // still two types of loadable items
+ assertEquals(rotator.size(), 2);
+
+ count++;
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((6 * 5 * 4 * 3 * 2 * 1) / ((4 * 3 * 2 * 1) * (2 * 1)), count);
+ }
+
+ @Test
+ void testLoadableItems() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withId("0").withWeight(1).build(), 2));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withId("1").withWeight(1).build(), 4));
+
+ List groups = new ArrayList<>();
+ groups.add(new StackableItemGroup("1", products));
+
+ MutableIndexedStackableItemGroupPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ rotator.remove(0, 1);
+
+ // still two types of loadable items
+ assertEquals(rotator.size(), 2);
+
+ int[] frequencies = toFrequency(rotator, 2);
+
+ assertEquals(1, frequencies[0]);
+ assertEquals(4, frequencies[1]);
+
+ // still two types of loadable items
+ rotator.remove(1, 2);
+ assertEquals(rotator.size(), 2);
+
+ frequencies = toFrequency(rotator, 2);
+ assertEquals(1, frequencies[0]);
+ assertEquals(2, frequencies[1]);
+
+ rotator.remove(0, 1);
+ // 0 exhausted
+ assertEquals(rotator.size(), 1);
+
+ frequencies = toFrequency(rotator, 2);
+ assertEquals(0, frequencies[0]);
+ assertEquals(2, frequencies[1]);
+
+ rotator.remove(0, 2);
+ // 1 exhausted
+ assertEquals(rotator.size(), 0);
+ }
+
+ public int[] toFrequency(MutableIndexedStackableItemGroupPermutationRotationIterator rotator, int size) {
+ int[] counts = new int[size];
+ for (int i : rotator.getPermutations()) {
+ counts[i]++;
+ }
+ return counts;
+ }
+
+
+
+
+}
diff --git a/core/src/test/java/com/github/skjolber/packing/iterator/MutableLoadablePermutationRotationIteratorTest.java b/core/src/test/java/com/github/skjolber/packing/iterator/MutableLoadablePermutationRotationIteratorTest.java
new file mode 100644
index 00000000..08234261
--- /dev/null
+++ b/core/src/test/java/com/github/skjolber/packing/iterator/MutableLoadablePermutationRotationIteratorTest.java
@@ -0,0 +1,164 @@
+package com.github.skjolber.packing.iterator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.github.skjolber.packing.api.Box;
+import com.github.skjolber.packing.api.Dimension;
+import com.github.skjolber.packing.api.StackableItem;
+import com.github.skjolber.packing.api.packager.StackableItems;
+import com.github.skjolber.packing.iterator.MutableIndexedStackableItemPermutationRotationIterator.Builder;
+import com.github.skjolber.packing.iterator.MutableIndexedStackableItemPermutationRotationIterator.DelegateBuilder;
+
+class MutableLoadablePermutationRotationIteratorTest extends AbstractStackableItemPermutationRotationIteratorTest {
+
+ @Override
+ public DelegateBuilder newBuilder() {
+ return new DelegateBuilder(DefaultStackableItemPermutationRotationIterator.newBuilder());
+ }
+
+ @Test
+ void testMutableRotationCount() {
+ for (int i = 1; i <= 8; i++) {
+ Dimension container = new Dimension(null, 3 * (i + 1), 3, 1);
+
+ List products1 = new ArrayList<>();
+
+ for (int k = 0; k < i; k++) {
+ Box box = Box.newBuilder().withSize(3, 1, 1).withRotate3D().withId(Integer.toString(k)).withWeight(1).build();
+
+ StackableItem item = new StackableItem(box);
+
+ products1.add(item);
+ }
+
+ MutableIndexedStackableItemPermutationRotationIterator rotator =
+ newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products1)
+ .withMaxLoadWeight(products1.size())
+ .build();
+
+ StackableItems items = rotator;
+
+ long unmodifiedRotationsCount = rotator.getIterator().countRotations();
+
+ long modifiedRotationsCount = rotator.countRotations();
+
+ assertTrue(unmodifiedRotationsCount >= modifiedRotationsCount);
+
+ long rotate = 0;
+ do {
+ // removing items do not affect the number of rotations
+ assertEquals(items.size(), products1.size());
+
+ items.remove(0, 1);
+ for(int k = 0; k < items.size(); k++) {
+ StackableItem item = items.get(k);
+ assertFalse(item.getStackable().getId().equals("0"));
+ }
+
+ rotate++;
+ } while (rotator.nextRotation() != -1);
+
+ assertEquals(unmodifiedRotationsCount, rotate);
+ }
+ }
+
+ @Test
+ void testMutablePermutationsWithMultipleBoxes() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withId("0").withWeight(1).build(), 2));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withId("1").withWeight(1).build(), 4));
+
+ MutableIndexedStackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+
+ int count = 0;
+ do {
+ assertEquals(rotator.size(), products.size());
+
+ // removing items do not affect the number of permutations
+ rotator.remove(0, 1);
+
+ // still two types of loadable items
+ assertEquals(rotator.size(), 2);
+
+ count++;
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((6 * 5 * 4 * 3 * 2 * 1) / ((4 * 3 * 2 * 1) * (2 * 1)), count);
+ }
+
+ @Test
+ void testLoadableItems() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List products = new ArrayList<>();
+
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withId("0").withWeight(1).build(), 2));
+ products.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withId("1").withWeight(1).build(), 4));
+
+ MutableIndexedStackableItemPermutationRotationIterator rotator = newBuilder()
+ .withLoadSize(container)
+ .withStackableItems(products)
+ .withMaxLoadWeight(products.size())
+ .build();
+
+ rotator.remove(0, 1);
+
+ // still two types of loadable items
+ assertEquals(rotator.size(), 2);
+
+ int[] frequencies = toFrequency(rotator, 2);
+
+ assertEquals(1, frequencies[0]);
+ assertEquals(4, frequencies[1]);
+
+ // still two types of loadable items
+ rotator.remove(1, 2);
+ assertEquals(rotator.size(), 2);
+
+ frequencies = toFrequency(rotator, 2);
+ assertEquals(1, frequencies[0]);
+ assertEquals(2, frequencies[1]);
+
+ rotator.remove(0, 1);
+ // 0 exhausted
+ assertEquals(rotator.size(), 1);
+
+ frequencies = toFrequency(rotator, 2);
+ assertEquals(0, frequencies[0]);
+ assertEquals(2, frequencies[1]);
+
+ rotator.remove(0, 2);
+ // 1 exhausted
+ assertEquals(rotator.size(), 0);
+ }
+
+ public int[] toFrequency(MutableIndexedStackableItemPermutationRotationIterator rotator, int size) {
+ int[] counts = new int[size];
+ for (int i : rotator.getPermutations()) {
+ counts[i]++;
+ }
+ return counts;
+ }
+
+
+
+
+}
diff --git a/core/src/test/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorListTest.java b/core/src/test/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorListTest.java
new file mode 100644
index 00000000..11fc68d2
--- /dev/null
+++ b/core/src/test/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorListTest.java
@@ -0,0 +1,330 @@
+package com.github.skjolber.packing.iterator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import com.github.skjolber.packing.api.Box;
+import com.github.skjolber.packing.api.Dimension;
+import com.github.skjolber.packing.api.StackableItem;
+import com.github.skjolber.packing.api.StackableItemGroup;
+
+public class ParallelStackableItemGroupPermutationRotationIteratorListTest {
+
+ @Test
+ void testPermutations() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ ParallelStackableItemGroupPermutationRotationIteratorList rotator = ParallelStackableItemGroupPermutationRotationIteratorList.newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .withParallelizationCount(5)
+ .build();
+
+ ParallelStackableItemGroupPermutationRotationIterator rotator2 = ParallelStackableItemGroupPermutationRotationIterator.newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .build();
+
+ int count = 0;
+ do {
+ count++;
+ System.out.println(Arrays.toString(rotator.getPermutations()) + " " + Arrays.toString(rotator2.getPermutations()));
+ } while (rotator.nextPermutation() != -1 && rotator2.nextPermutation() != -1);
+
+ assertEquals((3 * 2 * 1) * (3 * 2 * 1), count);
+
+ assertEquals(count, rotator.countPermutations());
+ }
+
+ @Test
+ void testPermutationsRepeatedItems() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("6").withWeight(1).build(), 2));
+ groups.add(new StackableItemGroup("2", products2));
+
+ ParallelStackableItemGroupPermutationRotationIteratorList rotator = ParallelStackableItemGroupPermutationRotationIteratorList.newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .withParallelizationCount(2)
+ .build();
+
+ int count = 0;
+ do {
+ count++;
+ System.out.println(Arrays.toString(rotator.getPermutations()));
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((3 * 2 * 1) * (5 * 4 * 3 * 2 * 1) / 2, count);
+
+ assertEquals(count, rotator.countPermutations());
+ }
+
+ @Test
+ void testRemovePermutations() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ ParallelStackableItemGroupPermutationRotationIteratorList rotator = ParallelStackableItemGroupPermutationRotationIteratorList.newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .withParallelizationCount(2)
+ .build();
+
+ rotator.removePermutations(1);
+
+ int count = 0;
+ do {
+ count++;
+ System.out.println(Arrays.toString(rotator.getPermutations()));
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((2 * 1) * (3 * 2 * 1), count);
+
+ assertEquals(count, rotator.countPermutations());
+ }
+
+ @Test
+ void testRemoveWholeGroup() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ ParallelStackableItemGroupPermutationRotationIteratorList rotator = ParallelStackableItemGroupPermutationRotationIteratorList.newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .withParallelizationCount(2)
+ .build();
+
+ rotator.removePermutations(1);
+ // remove the rest of the first group
+ rotator.removePermutations(2);
+
+ int count = 0;
+ do {
+ count++;
+ } while (rotator.nextPermutation() != -1);
+
+ assertEquals((3 * 2 * 1), count);
+
+ assertEquals(count, rotator.countPermutations());
+ }
+
+
+ @Test
+ void testNexPermutationMaxIndexGroup1() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+ ParallelStackableItemGroupPermutationRotationIteratorList iterator = ParallelStackableItemGroupPermutationRotationIteratorList.newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products1.size())
+ .withParallelizationCount(1)
+ .build();
+
+ int[] before = iterator.getPermutations();
+
+ int maxIndex = 3;
+
+ System.out.println(Arrays.toString(iterator.getPermutations()));
+
+ iterator.nextPermutation(maxIndex);
+
+ System.out.println(Arrays.toString(iterator.getPermutations()));
+
+ int[] after = iterator.getPermutations();
+ for(int i = 0; i < maxIndex; i++) {
+ assertEquals(before[i], after[i]);
+ }
+
+ assertNotEquals(before[maxIndex], after[maxIndex]);
+
+ // group 2 should be reset
+ for(int i = products1.size(); i < after.length - 1; i++) {
+ assertTrue(before[i] <= after[i + 1]);
+ }
+ }
+
+ @Test
+ void testNexPermutationMaxIndexGroup2() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("6").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("7").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("8").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ ParallelStackableItemGroupPermutationRotationIteratorList iterator = ParallelStackableItemGroupPermutationRotationIteratorList.newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .withParallelizationCount(2)
+ .build();
+
+ int[] before = iterator.getPermutations();
+
+ System.out.println(Arrays.toString(before));
+
+ int maxIndex = 6;
+
+ iterator.nextPermutation(maxIndex);
+
+ int[] after = iterator.getPermutations();
+
+ System.out.println(Arrays.toString(iterator.getPermutations()));
+
+ for(int i = 0; i < maxIndex; i++) {
+ assertEquals(before[i], after[i]);
+ }
+
+ assertNotEquals(before[maxIndex], after[maxIndex]);
+
+ // group 1 should not be touched
+ for(int i = 0; i < products1.size(); i++) {
+ assertEquals(before[i], after[i]);
+ }
+ }
+
+ @Test
+ void testNexPermutationMaxIndexTransitionGroup() {
+ Dimension container = new Dimension(null, 9, 1, 1);
+
+ List groups = new ArrayList<>();
+
+ List products1 = new ArrayList<>();
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("0").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("1").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("2").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("3").withWeight(1).build()));
+ products1.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("4").withWeight(1).build()));
+ groups.add(new StackableItemGroup("1", products1));
+
+
+ List products2 = new ArrayList<>();
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("5").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("6").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("7").withWeight(1).build()));
+ products2.add(new StackableItem(Box.newBuilder().withRotate3D().withSize(1, 1, 3).withDescription("8").withWeight(1).build()));
+ groups.add(new StackableItemGroup("2", products2));
+
+ ParallelStackableItemGroupPermutationRotationIteratorList iterator = ParallelStackableItemGroupPermutationRotationIteratorList.newBuilder()
+ .withLoadSize(container)
+ .withStackableItemGroups(groups)
+ .withMaxLoadWeight(products2.size())
+ .withParallelizationCount(2)
+ .build();
+
+ // go to the last permuation of the second group
+ for(int i = 0; i < 4 * 3 * 2 * 1 - 1; i++) {
+ iterator.nextPermutation();
+ }
+
+ int[] before = iterator.getPermutations();
+
+ System.out.println(Arrays.toString(iterator.getPermutations()));
+
+ int maxIndex = 6;
+
+ int index = iterator.nextPermutation(maxIndex);
+
+ assertTrue(index < products1.size());
+
+ int[] after = iterator.getPermutations();
+
+ System.out.println(Arrays.toString(after));
+
+ for(int i = 0; i < index; i++) {
+ assertEquals(before[i], after[i]);
+ }
+
+ assertNotEquals(before[index], after[index]);
+ }
+}
diff --git a/core/src/test/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorTest.java b/core/src/test/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorTest.java
new file mode 100644
index 00000000..a6240111
--- /dev/null
+++ b/core/src/test/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorTest.java
@@ -0,0 +1,25 @@
+package com.github.skjolber.packing.iterator;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+public class ParallelStackableItemGroupPermutationRotationIteratorTest extends AbstractStackableItemGroupPermutationRotationIteratorTest {
+
+ @Override
+ public ParallelStackableItemGroupPermutationRotationIterator.Builder newBuilder() {
+ return ParallelStackableItemGroupPermutationRotationIterator.newBuilder();
+ }
+
+ @Test
+ @Disabled
+ void testRemovePermutations() {
+
+ }
+
+ @Test
+ @Disabled
+ void testRemoveWholeGroup() {
+
+ }
+
+}
diff --git a/core/src/test/java/com/github/skjolber/packing/iterator/StackableItemPermutationRotationIteratorTest.java b/core/src/test/java/com/github/skjolber/packing/iterator/StackableItemPermutationRotationIteratorTest.java
new file mode 100644
index 00000000..b634d299
--- /dev/null
+++ b/core/src/test/java/com/github/skjolber/packing/iterator/StackableItemPermutationRotationIteratorTest.java
@@ -0,0 +1,12 @@
+package com.github.skjolber.packing.iterator;
+
+import com.github.skjolber.packing.iterator.DefaultStackableItemPermutationRotationIterator.Builder;
+
+class StackableItemPermutationRotationIteratorTest extends AbstractStackableItemPermutationRotationIteratorTest {
+
+ @Override
+ public Builder newBuilder() {
+ return DefaultStackableItemPermutationRotationIterator.newBuilder();
+ }
+
+}