diff --git a/api/src/main/java/com/github/skjolber/packing/api/Box.java b/api/src/main/java/com/github/skjolber/packing/api/Box.java index 25c9cd14..592420c4 100644 --- a/api/src/main/java/com/github/skjolber/packing/api/Box.java +++ b/api/src/main/java/com/github/skjolber/packing/api/Box.java @@ -52,8 +52,13 @@ protected BoxStackValue newStackValue(int dx, int dy, int dz, StackConstraint co protected final long minimumArea; protected final long maximumArea; - public Box(String id, String name, long volume, int weight, BoxStackValue[] stackValues) { - super(id, name); + protected final String id; + protected final String description; + + public Box(String id, String description, long volume, int weight, BoxStackValue[] stackValues) { + this.id = id; + this.description = description; + this.volume = volume; this.weight = weight; this.stackValues = stackValues; @@ -65,6 +70,16 @@ public Box(String id, String name, long volume, int weight, BoxStackValue[] stac boxStackValue.setStackable(this); } } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getId() { + return id; + } @Override public int getWeight() { @@ -88,6 +103,10 @@ public Box clone() { } return new Box(id, description, volume, weight, stackValues); } + + public StackValue getStackValue(int index) { + return stackValues[index]; + } @Override public long getMinimumArea() { diff --git a/api/src/main/java/com/github/skjolber/packing/api/Container.java b/api/src/main/java/com/github/skjolber/packing/api/Container.java index d81efa1d..7302d3bc 100644 --- a/api/src/main/java/com/github/skjolber/packing/api/Container.java +++ b/api/src/main/java/com/github/skjolber/packing/api/Container.java @@ -147,9 +147,13 @@ protected ContainerStackValue[] getStackValues() { protected final long minArea; protected final long maxArea; - public Container(String id, String name, long volume, int emptyWeight, long maxLoadVolume, int maxLoadWeight, long minArea, long maxArea) { - super(id, name); + protected final String id; + protected final String description; + public Container(String id, String description, long volume, int emptyWeight, long maxLoadVolume, int maxLoadWeight, long minArea, long maxArea) { + this.id = id; + this.description = description; + this.emptyWeight = emptyWeight; this.maxLoadVolume = maxLoadVolume; this.maxLoadWeight = maxLoadWeight; @@ -159,6 +163,16 @@ public Container(String id, String name, long volume, int emptyWeight, long maxL this.maxArea = maxArea; } + @Override + public String getDescription() { + return description; + } + + @Override + public String getId() { + return id; + } + @Override public int getWeight() { return emptyWeight + getStack().getWeight(); diff --git a/api/src/main/java/com/github/skjolber/packing/api/ContainerConstraint.java b/api/src/main/java/com/github/skjolber/packing/api/ContainerConstraint.java new file mode 100644 index 00000000..a148ac6d --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/ContainerConstraint.java @@ -0,0 +1,15 @@ +package com.github.skjolber.packing.api; + +import com.github.skjolber.packing.api.packager.StackableItemsFilterBuilder; + +/** + * + * Interface for handling which Stackables, or combinations of Stackables go into a Container. + * + */ + +public interface ContainerConstraint { + + StackableItemsFilterBuilder newLoadableFilterBuilder(); + +} diff --git a/api/src/main/java/com/github/skjolber/packing/api/DefaultContainer.java b/api/src/main/java/com/github/skjolber/packing/api/DefaultContainer.java index 32f97f9e..783a85ab 100644 --- a/api/src/main/java/com/github/skjolber/packing/api/DefaultContainer.java +++ b/api/src/main/java/com/github/skjolber/packing/api/DefaultContainer.java @@ -29,6 +29,10 @@ public ContainerStackValue[] getStackValues() { public Stack getStack() { return stack; } + + public ContainerStackValue getStackValue(int index) { + return stackValues[index]; + } @Override public DefaultContainer clone() { diff --git a/api/src/main/java/com/github/skjolber/packing/api/GravitySupport.java b/api/src/main/java/com/github/skjolber/packing/api/GravitySupport.java new file mode 100644 index 00000000..f726188a --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/GravitySupport.java @@ -0,0 +1,7 @@ +package com.github.skjolber.packing.api; + +public interface GravitySupport { + + void loaded(StackPlacement stackPlacement); + +} diff --git a/api/src/main/java/com/github/skjolber/packing/api/GravitySupportBuilder.java b/api/src/main/java/com/github/skjolber/packing/api/GravitySupportBuilder.java new file mode 100644 index 00000000..b4e41640 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/GravitySupportBuilder.java @@ -0,0 +1,37 @@ +package com.github.skjolber.packing.api; + +/** + * Builder scaffold. + * + * @see https://www.sitepoint.com/self-types-with-javas-generics/ + */ + +@SuppressWarnings("unchecked") +public abstract class GravitySupportBuilder> { + + protected Container container; + protected ContainerStackValue stackValue; + protected Stack stack; + protected StackPlacement stackPlacement; + + public B withContainer(Container container) { + this.container = container; + return (B)this; + } + + public B withStack(Stack stack) { + this.stack = stack; + return (B)this; + } + + public B withStackValue(ContainerStackValue stackValue) { + this.stackValue = stackValue; + return (B)this; + } + + public abstract GravitySupport build(); + + + +} diff --git a/api/src/main/java/com/github/skjolber/packing/api/PlacementSupportBuilder.java b/api/src/main/java/com/github/skjolber/packing/api/PlacementSupportBuilder.java new file mode 100644 index 00000000..5fa8b736 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/PlacementSupportBuilder.java @@ -0,0 +1,36 @@ +package com.github.skjolber.packing.api; + +/** + * Builder scaffold. + * + * @see https://www.sitepoint.com/self-types-with-javas-generics/ + */ + +@SuppressWarnings("unchecked") +public abstract class PlacementSupportBuilder> { + + protected Container container; + protected ContainerStackValue stackValue; + protected Stack stack; + + public B withContainer(Container container) { + this.container = container; + return (B)this; + } + + public B withStack(Stack stack) { + this.stack = stack; + return (B)this; + } + + public B withStackValue(ContainerStackValue stackValue) { + this.stackValue = stackValue; + return (B)this; + } + + public abstract GravitySupport build(); + + + +} diff --git a/api/src/main/java/com/github/skjolber/packing/api/StackValueConstraint.java b/api/src/main/java/com/github/skjolber/packing/api/StackValueConstraint.java new file mode 100644 index 00000000..8b473be6 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/StackValueConstraint.java @@ -0,0 +1,8 @@ +package com.github.skjolber.packing.api; + +public interface StackValueConstraint { + + GravitySupportBuilder newGravitySupportBuilder(); + + +} diff --git a/api/src/main/java/com/github/skjolber/packing/api/Stackable.java b/api/src/main/java/com/github/skjolber/packing/api/Stackable.java index c86ab82e..d7f4b89a 100644 --- a/api/src/main/java/com/github/skjolber/packing/api/Stackable.java +++ b/api/src/main/java/com/github/skjolber/packing/api/Stackable.java @@ -8,28 +8,15 @@ public abstract class Stackable implements Serializable { private static final long serialVersionUID = 1L; - protected final String id; - protected final String description; - - public Stackable(String id, String description) { - super(); - this.id = id; - this.description = description; - } - public abstract long getVolume(); public abstract int getWeight(); public abstract StackValue[] getStackValues(); - public String getDescription() { - return description; - } + public abstract String getDescription(); - public String getId() { - return id; - } + public abstract String getId(); public List fitsInside(Dimension bound) { List list = new ArrayList<>(); @@ -42,6 +29,8 @@ public List fitsInside(Dimension bound) { return list; } + + public abstract StackValue getStackValue(int index); @Override public abstract Stackable clone(); diff --git a/api/src/main/java/com/github/skjolber/packing/api/StackableItem.java b/api/src/main/java/com/github/skjolber/packing/api/StackableItem.java index f0670a00..aff03dcc 100644 --- a/api/src/main/java/com/github/skjolber/packing/api/StackableItem.java +++ b/api/src/main/java/com/github/skjolber/packing/api/StackableItem.java @@ -11,8 +11,8 @@ public class StackableItem implements Serializable { private static final long serialVersionUID = 1L; - private final int count; - private final Stackable stackable; + protected int count; + protected final Stackable stackable; public StackableItem(Stackable box) { this(box, 1); @@ -36,5 +36,21 @@ public Stackable getStackable() { public String toString() { return String.format("%dx%s", count, stackable); } + + public void decrement() { + count--; + } + + public boolean isEmpty() { + return count == 0; + } + + public void decrement(int value) { + this.count = this.count - value; + } + + public StackableItem clone() { + return new StackableItem(stackable, count); + } } diff --git a/api/src/main/java/com/github/skjolber/packing/api/StackableItemGroup.java b/api/src/main/java/com/github/skjolber/packing/api/StackableItemGroup.java new file mode 100644 index 00000000..fcab9dba --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/StackableItemGroup.java @@ -0,0 +1,88 @@ +package com.github.skjolber.packing.api; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * Items which belong together, for example different parts of a single product or order. + * + */ + +public class StackableItemGroup { + + private String id; + + private List items; + + public StackableItemGroup(String id, List items) { + super(); + this.id = id; + this.items = items; + } + + public String getId() { + return id; + } + + public List getItems() { + return items; + } + + public void setId(String id) { + this.id = id; + } + + public void setItems(List items) { + this.items = items; + } + + public int size() { + return items.size(); + } + + public StackableItem get(int i) { + return items.get(i); + } + + + public int stackableItemsCount() { + int count = 0; + for (StackableItem loadableItem : items) { + count += loadableItem.getCount(); + } + return count; + } + + public boolean isEmpty() { + for (StackableItem loadableItem : items) { + if(!loadableItem.isEmpty()) { + return false; + } + } + + return true; + } + + public void removeEmpty() { + for (int j = 0; j < items.size(); j++) { + StackableItem loadableItem = items.get(j); + + if(loadableItem.isEmpty()) { + items.remove(j); + j--; + } + } + } + + public StackableItemGroup clone() { + List items = new ArrayList<>(); + + for (StackableItem stackableItem : this.items) { + items.add(stackableItem.clone()); + } + + return new StackableItemGroup(id, items); + } + +} \ No newline at end of file diff --git a/api/src/main/java/com/github/skjolber/packing/api/ep/Point3DFilter.java b/api/src/main/java/com/github/skjolber/packing/api/ep/Point3DFilter.java new file mode 100644 index 00000000..26378962 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/ep/Point3DFilter.java @@ -0,0 +1,15 @@ +package com.github.skjolber.packing.api.ep; +import java.util.List; + +import com.github.skjolber.packing.api.ep.Point3D; + +/** + * Constraint describing where a box can be placed within a stack. + * For example, a flammable item must be stacked by the door. + */ + +public interface Point3DFilter { + + List filter(List points); + +} \ No newline at end of file diff --git a/api/src/main/java/com/github/skjolber/packing/api/packager/BoundedStackable.java b/api/src/main/java/com/github/skjolber/packing/api/packager/BoundedStackable.java new file mode 100644 index 00000000..38a25517 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/packager/BoundedStackable.java @@ -0,0 +1,83 @@ +package com.github.skjolber.packing.api.packager; + +import java.util.List; + +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.Stackable; + +/** + * + * Stackable item which fit within certain bounds, i.e. load dimensions of a container. + * + */ + +public class BoundedStackable extends Stackable { + + private static final long serialVersionUID = 1L; + + protected final StackValue[] values; + protected final Stackable stackable; + + protected final long minimumArea; + protected final long maximumArea; + + public BoundedStackable(Stackable stackable, StackValue[] stackValues) { + this.values = stackValues; + this.stackable = stackable; + + this.minimumArea = getMinimumArea(stackValues); + this.maximumArea = getMinimumArea(stackValues); + } + + public BoundedStackable(Stackable stackable, List stackValues) { + this(stackable, stackValues.toArray(new StackValue[stackValues.size()])); + } + + public Stackable getStackable() { + return stackable; + } + + public StackValue getStackValue(int index) { + return values[index]; + } + + @Override + public long getVolume() { + return stackable.getVolume(); + } + + @Override + public int getWeight() { + return stackable.getWeight(); + } + + @Override + public StackValue[] getStackValues() { + return values; + } + + @Override + public String getDescription() { + return stackable.getDescription(); + } + + @Override + public String getId() { + return stackable.getId(); + } + + @Override + public Stackable clone() { + return new BoundedStackable(stackable.clone(), values); + } + + @Override + public long getMinimumArea() { + return minimumArea; + } + + @Override + public long getMaximumArea() { + return maximumArea; + } +} diff --git a/api/src/main/java/com/github/skjolber/packing/api/packager/DefaultStackableItemsFilter.java b/api/src/main/java/com/github/skjolber/packing/api/packager/DefaultStackableItemsFilter.java new file mode 100644 index 00000000..fa40fe81 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/packager/DefaultStackableItemsFilter.java @@ -0,0 +1,16 @@ +package com.github.skjolber.packing.api.packager; + +public class DefaultStackableItemsFilter implements StackableItemsFilter { + + protected final StackableItems loadableItems; + + public DefaultStackableItemsFilter(StackableItems loadableItems) { + this.loadableItems = loadableItems; + } + + @Override + public void loaded(int index) { + // do nothing + } + +} \ No newline at end of file diff --git a/api/src/main/java/com/github/skjolber/packing/api/PlacementComparator.java b/api/src/main/java/com/github/skjolber/packing/api/packager/PlacementComparator.java similarity index 68% rename from api/src/main/java/com/github/skjolber/packing/api/PlacementComparator.java rename to api/src/main/java/com/github/skjolber/packing/api/packager/PlacementComparator.java index fb97b6df..14980053 100644 --- a/api/src/main/java/com/github/skjolber/packing/api/PlacementComparator.java +++ b/api/src/main/java/com/github/skjolber/packing/api/packager/PlacementComparator.java @@ -1,9 +1,13 @@ -package com.github.skjolber.packing.api; +package com.github.skjolber.packing.api.packager; import java.util.Comparator; +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.Stackable; +import com.github.skjolber.packing.api.ep.Point3D; + @FunctionalInterface -public interface PlacementComparator { +public interface PlacementComparator { /** * See {@linkplain Comparator#compare(Object, Object)}.
@@ -22,6 +26,6 @@ public interface PlacementComparator { * being compared by this comparator. */ - int compare(Stackable s1, StackValue sv1, L l1, Stackable s2, StackValue sv2, L l2); + int compare(Stackable s1, StackValue sv1, Point3D l1, Stackable s2, StackValue sv2, Point3D l2); } diff --git a/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItems.java b/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItems.java new file mode 100644 index 00000000..191c2416 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItems.java @@ -0,0 +1,19 @@ +package com.github.skjolber.packing.api.packager; + +import com.github.skjolber.packing.api.StackableItem; + +/** + * + * The items which are available for load into some particular container. + * + */ + +public interface StackableItems { + + int size(); + + StackableItem get(int index); + + void remove(int index, int count); + +} diff --git a/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItemsFilter.java b/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItemsFilter.java new file mode 100644 index 00000000..6ec9d7f4 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItemsFilter.java @@ -0,0 +1,21 @@ +package com.github.skjolber.packing.api.packager; + +/** + * + * The items which are available for load into some particular container. + * + */ + +public interface StackableItemsFilter { + + /** + * + * Notify stackable was loaded + * + * @param index + * @return true if some loadable item was excluded due to this loaded item + */ + + void loaded(int index); + +} diff --git a/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItemsFilterBuilder.java b/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItemsFilterBuilder.java new file mode 100644 index 00000000..db00a119 --- /dev/null +++ b/api/src/main/java/com/github/skjolber/packing/api/packager/StackableItemsFilterBuilder.java @@ -0,0 +1,46 @@ +package com.github.skjolber.packing.api.packager; + +import com.github.skjolber.packing.api.Container; +import com.github.skjolber.packing.api.ContainerStackValue; +import com.github.skjolber.packing.api.Stack; + +/** + * Builder scaffold. + * + * @see https://www.sitepoint.com/self-types-with-javas-generics/ + */ + +@SuppressWarnings("unchecked") +public abstract class StackableItemsFilterBuilder> { + + protected Stack stack; + protected Container container; + protected ContainerStackValue stackValue; + protected StackableItems loadableItems; + + public B withLoadableItems(StackableItems loadableItems) { + this.loadableItems = loadableItems; + return (B)this; + } + + public B withContainer(Container container) { + this.container = container; + return (B)this; + } + + public B withStack(Stack stack) { + this.stack = stack; + return (B)this; + } + + public B withStackValue(ContainerStackValue stackValue) { + this.stackValue = stackValue; + return (B)this; + } + + public abstract StackableItemsFilter build(); + + + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupIteratorBuilder.java b/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupIteratorBuilder.java new file mode 100644 index 00000000..95b098dc --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupIteratorBuilder.java @@ -0,0 +1,108 @@ +package com.github.skjolber.packing.iterator; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import com.github.skjolber.packing.api.Dimension; +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.Stackable; +import com.github.skjolber.packing.api.StackableItem; +import com.github.skjolber.packing.api.StackableItemGroup; +import com.github.skjolber.packing.api.packager.BoundedStackable; + +/** + * Builder scaffold. + * + * @see https://www.sitepoint.com/self-types-with-javas-generics/ + */ + +public abstract class AbstractStackableItemGroupIteratorBuilder> { + + protected int maxLoadWeight = -1; + protected Predicate filter; + protected Dimension size; + protected List stackableItemGroups; + + public B withSize(int dx, int dy, int dz) { + this.size = new Dimension(dx, dy, dz); + + return (B)this; + } + + public B withLoadSize(Dimension dimension) { + this.size = dimension; + + return (B)this; + } + + public B withFilter(Predicate filter) { + this.filter = filter; + + return (B)this; + } + + public B withMaxLoadWeight(int maxLoadWeight) { + this.maxLoadWeight = maxLoadWeight; + + return (B)this; + } + + public B withStackableItemGroups(List stackableItems) { + this.stackableItemGroups = stackableItems; + + return (B)this; + } + + protected List toMatrix() { + List results = new ArrayList<>(stackableItemGroups.size()); + + int offset = 0; + + for (int i = 0; i < stackableItemGroups.size(); i++) { + + StackableItemGroup group = stackableItemGroups.get(i); + + List loadableItems = new ArrayList<>(group.size()); + for (int k = 0; k < group.size(); k++) { + StackableItem item = group.get(k); + + if(item.getCount() == 0) { + continue; + } + + Stackable stackable = item.getStackable(); + if(stackable.getWeight() > maxLoadWeight) { + continue; + } + + if(stackable.getVolume() > size.getVolume()) { + continue; + } + + List boundRotations = stackable.rotations(size); + if(boundRotations == null || boundRotations.isEmpty()) { + continue; + } + + if(filter != null && !filter.test(stackable)) { + continue; + } + + BoundedStackable loadable = new BoundedStackable(stackable, boundRotations); + + loadableItems.add(new IndexedStackableItem(loadable, item.getCount(), offset)); + + offset++; + } + if(!loadableItems.isEmpty()) { + results.add(new IndexedStackableItemGroup(group.getId(), loadableItems)); + } + } + return results; + } + + public abstract StackableItemGroupPermutationRotationIterator build(); + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupPermutationRotationIterator.java new file mode 100644 index 00000000..a3f9957f --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemGroupPermutationRotationIterator.java @@ -0,0 +1,130 @@ +package com.github.skjolber.packing.iterator; + +import java.util.List; + +import com.github.skjolber.packing.api.StackableItem; + +public abstract class AbstractStackableItemGroupPermutationRotationIterator extends AbstractStackableItemPermutationRotationIterator implements StackableItemGroupPermutationRotationIterator { + + protected List groups; + + public AbstractStackableItemGroupPermutationRotationIterator(IndexedStackableItem[] matrix, List groups) { + super(matrix); + this.groups = groups; + } + + protected int getCount() { + int index = 0; + for (IndexedStackableItemGroup loadableItemGroup : groups) { + index += loadableItemGroup.stackableItemsCount(); + } + return index; + } + + /** + * Return number of permutations for boxes which fit within this container. + * + * @return permutation count + */ + + public long countPermutations() { + // reduce permutations for boxes which are duplicated + + // could be further bounded by looking at how many boxes (i.e. n x the smallest) which actually + // fit within the container volume + long n = 1; + + for (IndexedStackableItemGroup loadableItemGroup : groups) { + + List items = loadableItemGroup.getItems(); + + int count = loadableItemGroup.stackableItemsCount(); + + if(count == 0) { + continue; + } + + int maxCount = 0; + for (StackableItem value : items) { + if(value != null) { + if(maxCount < value.getCount()) { + maxCount = value.getCount(); + } + } + } + + if(maxCount > 1) { + int[] factors = new int[maxCount]; + for (StackableItem value : items) { + if(value != null) { + for (int k = 0; k < value.getCount(); k++) { + factors[k]++; + } + } + } + + for (long i = 0; i < count; i++) { + if(Long.MAX_VALUE / (i + 1) <= n) { + return -1L; + } + + n = n * (i + 1); + + for (int k = 1; k < maxCount; k++) { + while (factors[k] > 0 && n % (k + 1) == 0) { + n = n / (k + 1); + + factors[k]--; + } + } + } + + for (int k = 1; k < maxCount; k++) { + while (factors[k] > 0) { + n = n / (k + 1); + + factors[k]--; + } + } + } else { + for (long i = 0; i < count; i++) { + if(Long.MAX_VALUE / (i + 1) <= n) { + return -1L; + } + n = n * (i + 1); + } + } + } + return n; + } + + + @Override + public void removePermutations(List removed) { + for (Integer i : removed) { + IndexedStackableItem loadableItem = stackableItems[i]; + + loadableItem.decrement(); + + if(loadableItem.isEmpty()) { + stackableItems[i] = null; + } + } + + // go through all groups and clean up + for(int i = 0; i < groups.size(); i++) { + IndexedStackableItemGroup group = groups.get(i); + + group.removeEmpty(); + if(group.isEmpty()) { + groups.remove(i); + i--; + } + } + } + + public List getGroups() { + return groups; + } + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemIteratorBuilder.java b/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemIteratorBuilder.java new file mode 100644 index 00000000..7236d97d --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemIteratorBuilder.java @@ -0,0 +1,93 @@ +package com.github.skjolber.packing.iterator; + +import java.util.List; +import java.util.function.Predicate; + +import com.github.skjolber.packing.api.Dimension; +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.Stackable; +import com.github.skjolber.packing.api.StackableItem; +import com.github.skjolber.packing.api.packager.BoundedStackable; + +/** + * Builder scaffold. + * + * @see https://www.sitepoint.com/self-types-with-javas-generics/ + */ + +public abstract class AbstractStackableItemIteratorBuilder> { + + protected int maxLoadWeight = -1; + protected Predicate filter; + protected Dimension size; + protected List stackableItems; + + public B withSize(int dx, int dy, int dz) { + this.size = new Dimension(dx, dy, dz); + + return (B)this; + } + + public B withLoadSize(Dimension dimension) { + this.size = dimension; + + return (B)this; + } + + public B withFilter(Predicate filter) { + this.filter = filter; + + return (B)this; + } + + public B withMaxLoadWeight(int maxLoadWeight) { + this.maxLoadWeight = maxLoadWeight; + + return (B)this; + } + + public B withStackableItems(List stackableItems) { + this.stackableItems = stackableItems; + + return (B)this; + } + + protected IndexedStackableItem[] toMatrix() { + IndexedStackableItem[] results = new IndexedStackableItem[stackableItems.size()]; + + for (int i = 0; i < stackableItems.size(); i++) { + StackableItem item = stackableItems.get(i); + + if(item.getCount() == 0) { + continue; + } + + Stackable stackable = item.getStackable(); + if(stackable.getWeight() > maxLoadWeight) { + continue; + } + + if(stackable.getVolume() > size.getVolume()) { + continue; + } + + List boundRotations = stackable.rotations(size); + if(boundRotations == null || boundRotations.isEmpty()) { + continue; + } + + if(filter != null && !filter.test(stackable)) { + continue; + } + + BoundedStackable loadable = new BoundedStackable(stackable, boundRotations); + + results[i] = new IndexedStackableItem(loadable, item.getCount(), i); + } + return results; + } + + public abstract I build(); + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemPermutationRotationIterator.java new file mode 100644 index 00000000..4dafc304 --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/AbstractStackableItemPermutationRotationIterator.java @@ -0,0 +1,205 @@ +package com.github.skjolber.packing.iterator; + +import java.util.ArrayList; +import java.util.List; + +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.StackableItem; + +public abstract class AbstractStackableItemPermutationRotationIterator implements StackableItemPermutationRotationIterator { + + protected final IndexedStackableItem[] stackableItems; // by index + + protected int[] rotations; // 2^n or 6^n + protected int[] reset; + + // permutations of boxes that fit inside this container + protected int[] permutations; // n! + + // minimum volume from index i and above + protected long[] minStackableVolume; + + public AbstractStackableItemPermutationRotationIterator(IndexedStackableItem[] matrix) { + this.stackableItems = matrix; + } + + /** + * Get number of box items within the constraints. + * + * @return number between 0 and number of {@linkplain StackableItem}s used in the constructor. + */ + + public int boxItemLength() { + return stackableItems.length; + } + + public long getMinStackableArea(int offset) { + long minArea = Long.MAX_VALUE; + for (int i = offset; i < length(); i++) { + StackValue permutationRotation = getStackValue(i); + long area = permutationRotation.getArea(); + if(area < minArea) { + minArea = area; + } + } + return minArea; + } + + public int getMinStackableAreaIndex(int offset) { + long minArea = getStackValue(offset).getArea(); + int index = offset; + + for (int i = offset + 1; i < length(); i++) { + StackValue permutationRotation = getStackValue(i); + long area = permutationRotation.getArea(); + if(area < minArea) { + minArea = area; + index = i; + } + } + return index; + } + + public List get(PermutationRotationState state, int length) { + int[] permutations = state.getPermutations(); + int[] rotations = state.getRotations(); + + List results = new ArrayList(length); + for (int i = 0; i < length; i++) { + results.add(stackableItems[permutations[i]].getStackable().getStackValue(rotations[i])); + } + return results; + } + + public abstract int length(); + + protected int[] calculateFrequencies() { + int[] frequencies = new int[stackableItems.length]; + + for (int i = 0; i < stackableItems.length; i++) { + if(stackableItems[i] != null) { + frequencies[i] = stackableItems[i].getCount(); + } + } + return frequencies; + } + + + public long countRotations() { + int[] permutations = getPermutations(); + + long n = 1; + for (int i = 0; i < permutations.length; i++) { + IndexedStackableItem value = stackableItems[permutations[i]]; + if(Long.MAX_VALUE / value.getStackable().getStackValues().length <= n) { + return -1L; + } + + n = n * value.getStackable().getStackValues().length; + } + return n; + } + + /** + * Return number of permutations for boxes which fit within this container. + * + * @return permutation count + */ + + public long countPermutations() { + // reduce permutations for boxes which are duplicated + + // could be further bounded by looking at how many boxes (i.e. n x the smallest) which actually + // fit within the container volume + + int[] permutations = getPermutations(); + + int maxCount = 0; + for (IndexedStackableItem value : stackableItems) { + if(value != null) { + if(maxCount < value.getCount()) { + maxCount = value.getCount(); + } + } + } + + long n = 1; + if(maxCount > 1) { + int[] factors = new int[maxCount]; + for (IndexedStackableItem value : stackableItems) { + if(value != null) { + for (int k = 0; k < value.getCount(); k++) { + factors[k]++; + } + } + } + + for (long i = 0; i < permutations.length; i++) { + if(Long.MAX_VALUE / (i + 1) <= n) { + return -1L; + } + + n = n * (i + 1); + + for (int k = 1; k < maxCount; k++) { + while (factors[k] > 0 && n % (k + 1) == 0) { + n = n / (k + 1); + + factors[k]--; + } + } + } + + for (int k = 1; k < maxCount; k++) { + while (factors[k] > 0) { + n = n / (k + 1); + + factors[k]--; + } + } + } else { + for (long i = 0; i < permutations.length; i++) { + if(Long.MAX_VALUE / (i + 1) <= n) { + return -1L; + } + n = n * (i + 1); + } + } + return n; + } + + public IndexedStackableItem[] getStackableItems() { + return stackableItems; + } + public long getMinStackableVolume(int offset) { + return minStackableVolume[offset]; + } + + public long[] getMinStackableVolume() { + return minStackableVolume; + } + + protected void calculateMinStackableVolume(int offset) { + StackValue last = stackableItems[permutations[permutations.length - 1]].getStackable().getStackValue(rotations[permutations.length - 1]); + + minStackableVolume[permutations.length - 1] = last.getVolume(); + + for (int i = permutations.length - 2; i >= offset; i--) { + long volume = stackableItems[permutations[i]].getStackable().getStackValue(rotations[i]).getVolume(); + + if(volume < minStackableVolume[i + 1]) { + minStackableVolume[i] = volume; + } else { + minStackableVolume[i] = minStackableVolume[i + 1]; + } + } + } + + @Override + public StackValue getStackValue(int index) { + return stackableItems[permutations[index]].getStackable().getStackValue(rotations[index]); + } + + + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/DefaultStackableItemGroupPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/DefaultStackableItemGroupPermutationRotationIterator.java new file mode 100644 index 00000000..12c4dd7d --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/DefaultStackableItemGroupPermutationRotationIterator.java @@ -0,0 +1,312 @@ +package com.github.skjolber.packing.iterator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.StackableItem; + +public class DefaultStackableItemGroupPermutationRotationIterator extends AbstractStackableItemGroupPermutationRotationIterator { + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder extends AbstractStackableItemGroupIteratorBuilder { + + public DefaultStackableItemGroupPermutationRotationIterator build() { + if(maxLoadWeight == -1) { + throw new IllegalStateException(); + } + if(size == null) { + throw new IllegalStateException(); + } + + List groups = toMatrix(); + + List matrix = new ArrayList<>(); + for (IndexedStackableItemGroup loadableItemGroup : groups) { + matrix.addAll(loadableItemGroup.getItems()); + } + + return new DefaultStackableItemGroupPermutationRotationIterator(groups, matrix.toArray(new IndexedStackableItem[matrix.size()])); + } + + } + + public DefaultStackableItemGroupPermutationRotationIterator(List groups, IndexedStackableItem[] matrix) { + super(matrix, groups); + + int count = getCount(); + + this.minStackableVolume = new long[count]; + + initiatePermutation(count); + } + + public StackValue getStackValue(int index) { + return stackableItems[permutations[index]].getStackable().getStackValue(rotations[index]); + } + + public void removePermutations(int count) { + // discard a number of items from the front + for(int i = 0; i < count; i++) { + IndexedStackableItem item = stackableItems[permutations[i]]; + + item.decrement(); + + if(item.isEmpty()) { + stackableItems[i] = null; + } + } + + for(int i = 0; i < groups.size(); i++) { + IndexedStackableItemGroup group = groups.get(i); + + group.removeEmpty(); + + if(group.isEmpty()) { + groups.remove(i); + i--; + } else { + break; + } + } + + initiatePermutation(rotations.length - count); + } + + protected void initiatePermutation(int remainingCount) { + this.rotations = new int[remainingCount]; + this.reset = new int[rotations.length]; + + // need to be in ascending order for the algorithm to work + int[] permutations = new int[rotations.length]; + + int offset = 0; + for (int j = 0; j < stackableItems.length; j++) { + IndexedStackableItem value = stackableItems[j]; + if(value != null && !value.isEmpty()) { + for (int k = 0; k < value.getCount(); k++) { + permutations[offset] = j; + offset++; + } + } + } + + this.permutations = permutations; + + if(permutations.length > 0) { + calculateMinStackableVolume(0); + } + } + + public long getMinStackableVolume(int offset) { + return minStackableVolume[offset]; + } + + /** + * Remove permutations, if present. + */ + + public void removePermutations(List removed) { + + super.removePermutations(removed); + + initiatePermutation(rotations.length - removed.size()); + } + + + public int nextRotation() { + // next rotation + return nextRotation(rotations.length - 1); + } + + public int nextRotation(int maxIndex) { + // next rotation + for (int i = maxIndex; i >= 0; i--) { + if(rotations[i] < stackableItems[permutations[i]].getStackable().getStackValues().length - 1) { + rotations[i]++; + + System.arraycopy(reset, 0, rotations, i + 1, rotations.length - (i + 1)); + + return i; + } + } + + return -1; + } + + @Override + public int[] getPermutations() { + int[] permutations = new int[this.permutations.length]; + System.arraycopy(this.permutations, 0, permutations, 0, permutations.length); + return permutations; + } + + protected void resetRotations() { + System.arraycopy(reset, 0, rotations, 0, rotations.length); + } + + public int nextPermutation(int maxIndex) { + int[] permutations = this.permutations; + + int limit = permutations.length; + + for(int g = groups.size() - 1; g >= 0; g--) { + IndexedStackableItemGroup loadableItemGroup = groups.get(g); + + // Find longest non-increasing suffix + int startIndex = limit - loadableItemGroup.stackableItemsCount(); + + if(startIndex <= maxIndex && maxIndex < limit) { + while (maxIndex >= startIndex) { + + int current = permutations[maxIndex]; + + // find the lexicographically next item to the right of the max index + int minIndex = -1; + for (int i = maxIndex + 1; i < limit; i++) { + if(permutations[i] > current && (minIndex == -1 || permutations[i] < permutations[minIndex])) { + minIndex = i; + } + } + + // if there is no such item, decrement and try again + if(minIndex == -1) { + // TODO search backwards? + maxIndex--; + + continue; + } + + // increment to the next lexigrapically item + // and sort the items to the right of the max index + permutations[maxIndex] = permutations[minIndex]; + permutations[minIndex] = current; + + // TODO: better to recreate? + Arrays.sort(permutations, maxIndex + 1, limit); + + resetRotations(); + + calculateMinStackableVolume(maxIndex); + + return maxIndex; + } + } + // reset current group + // TODO system arraycopy? + int i = startIndex; + + for (StackableItem loadableItem : loadableItemGroup.getItems()) { + IndexedStackableItem indexedStackableItem = (IndexedStackableItem)loadableItem; + for(int k = 0; k < indexedStackableItem.getCount(); k++) { + permutations[i] = indexedStackableItem.getIndex(); + + i++; + } + } + + // skip to next group + limit = startIndex; + } + + return -1; + } + + public int nextPermutation() { + resetRotations(); + + int[] permutations = this.permutations; + + int endIndex = permutations.length - 1; + + for(int g = groups.size() - 1; g >= 0; g--) { + IndexedStackableItemGroup loadableItemGroup = groups.get(g); + + // Find longest non-increasing suffix + int i = endIndex; + int startIndex = endIndex - loadableItemGroup.stackableItemsCount() + 1; + + while (i > startIndex && permutations[i - 1] >= permutations[i]) + i--; + // Now i is the head index of the suffix + + // Are we at the last permutation already? + if(i <= startIndex) { + // reset current group + // TODO system arraycopy? + i = startIndex; + + for (StackableItem loadableItem : loadableItemGroup.getItems()) { + IndexedStackableItem indexedStackableItem = (IndexedStackableItem)loadableItem; + for(int k = 0; k < indexedStackableItem.getCount(); k++) { + permutations[i] = indexedStackableItem.getIndex(); + + i++; + } + } + + // skip to next group + endIndex = startIndex - 1; + + continue; + } + + // Let array[i - 1] be the pivot + // Find rightmost element that exceeds the pivot + int j = endIndex; + while (permutations[j] <= permutations[i - 1]) + j--; + // Now the value array[j] will become the new pivot + // Assertion: j >= i + + int head = i - 1; + + // Swap the pivot with j + int temp = permutations[i - 1]; + permutations[i - 1] = permutations[j]; + permutations[j] = temp; + + // Reverse the suffix + j = endIndex; + while (i < j) { + temp = permutations[i]; + permutations[i] = permutations[j]; + permutations[j] = temp; + i++; + j--; + } + + calculateMinStackableVolume(head); + + // Successfully computed the next permutation + return head; + } + + return -1; + } + + public int length() { + return permutations.length; + } + + public PermutationRotationState getState() { + return new PermutationRotationState(rotations, permutations); + } + + public IndexedStackableItem getPermutation(int index) { + return stackableItems[permutations[index]]; + } + + protected int[] getRotations() { + return rotations; + } + + public List getGroups() { + return groups; + } + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/DefaultStackableItemPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/DefaultStackableItemPermutationRotationIterator.java new file mode 100644 index 00000000..514a7957 --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/DefaultStackableItemPermutationRotationIterator.java @@ -0,0 +1,248 @@ +package com.github.skjolber.packing.iterator; + +import java.util.Arrays; +import java.util.List; + +import com.github.skjolber.packing.api.StackValue; + +public class DefaultStackableItemPermutationRotationIterator extends AbstractStackableItemPermutationRotationIterator { + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder extends AbstractStackableItemIteratorBuilder { + + public DefaultStackableItemPermutationRotationIterator build() { + if(maxLoadWeight == -1) { + throw new IllegalStateException(); + } + if(size == null) { + throw new IllegalStateException(); + } + + IndexedStackableItem[] matrix = toMatrix(); + + return new DefaultStackableItemPermutationRotationIterator(matrix); + } + } + + public DefaultStackableItemPermutationRotationIterator(IndexedStackableItem[] matrix) { + super(matrix); + + int count = 0; + + for (IndexedStackableItem loadableItem : matrix) { + if(loadableItem != null) { + count += loadableItem.getCount(); + } + } + + this.minStackableVolume = new long[count]; + + initiatePermutation(count); + } + + public StackValue getStackValue(int index) { + return stackableItems[permutations[index]].getStackable().getStackValue(rotations[index]); + } + + public void removePermutations(int count) { + // discard a number of items from the front + for(int i = 0; i < count; i++) { + IndexedStackableItem loadableItem = stackableItems[permutations[i]]; + + loadableItem.decrement(); + + if(loadableItem.isEmpty()) { + stackableItems[i] = null; + } + } + + initiatePermutation(rotations.length - count); + } + + protected void initiatePermutation(int remainingCount) { + this.rotations = new int[remainingCount]; + this.reset = new int[rotations.length]; + + // need to be in ascending order for the algorithm to work + int[] permutations = new int[rotations.length]; + + int offset = 0; + for (int j = 0; j < stackableItems.length; j++) { + IndexedStackableItem value = stackableItems[j]; + if(value != null && !value.isEmpty()) { + for (int k = 0; k < value.getCount(); k++) { + permutations[offset] = j; + offset++; + } + } + } + + this.permutations = permutations; + + if(permutations.length > 0) { + calculateMinStackableVolume(0); + } + } + + public long getMinStackableVolume(int offset) { + return minStackableVolume[offset]; + } + + /** + * Remove permutations, if present. + */ + + public void removePermutations(List removed) { + + for (Integer i : removed) { + IndexedStackableItem loadableItem = stackableItems[i]; + + loadableItem.decrement(); + + if(loadableItem.isEmpty()) { + stackableItems[i] = null; + } + } + + initiatePermutation(rotations.length - removed.size()); + } + + + public int nextRotation() { + // next rotation + return nextRotation(rotations.length - 1); + } + + public int nextRotation(int maxIndex) { + // next rotation + for (int i = maxIndex; i >= 0; i--) { + if(rotations[i] < stackableItems[permutations[i]].getStackable().getStackValues().length - 1) { + rotations[i]++; + + System.arraycopy(reset, 0, rotations, i + 1, rotations.length - (i + 1)); + + return i; + } + } + + return -1; + } + + + @Override + public int[] getPermutations() { + int[] permutations = new int[this.permutations.length]; + System.arraycopy(this.permutations, 0, permutations, 0, permutations.length); + return permutations; + } + + protected void resetRotations() { + System.arraycopy(reset, 0, rotations, 0, rotations.length); + } + + public int nextPermutation(int maxIndex) { + while (maxIndex >= 0) { + + int[] permutations = this.permutations; + + int current = permutations[maxIndex]; + + // find the lexicographically next item to the right of the max index + int minIndex = -1; + for (int i = maxIndex + 1; i < permutations.length; i++) { + if(permutations[i] > current && (minIndex == -1 || permutations[i] < permutations[minIndex])) { + minIndex = i; + } + } + + // if there is no such item, decrement and try again + if(minIndex == -1) { + // TODO search backwards? + maxIndex--; + + continue; + } + + // increment to the next lexigrapically item + // and sort the items to the right of the max index + permutations[maxIndex] = permutations[minIndex]; + permutations[minIndex] = current; + + Arrays.sort(permutations, maxIndex + 1, permutations.length); + + resetRotations(); + + calculateMinStackableVolume(maxIndex); + + return maxIndex; + } + return -1; + } + + + public int nextPermutation() { + resetRotations(); + + int[] permutations = this.permutations; + + // Find longest non-increasing suffix + int i = permutations.length - 1; + while (i > 0 && permutations[i - 1] >= permutations[i]) + i--; + // Now i is the head index of the suffix + + // Are we at the last permutation already? + if(i <= 0) { + return -1; + } + + // Let array[i - 1] be the pivot + // Find rightmost element that exceeds the pivot + int j = permutations.length - 1; + while (permutations[j] <= permutations[i - 1]) + j--; + // Now the value array[j] will become the new pivot + // Assertion: j >= i + + int head = i - 1; + + // Swap the pivot with j + int temp = permutations[i - 1]; + permutations[i - 1] = permutations[j]; + permutations[j] = temp; + + // Reverse the suffix + j = permutations.length - 1; + while (i < j) { + temp = permutations[i]; + permutations[i] = permutations[j]; + permutations[j] = temp; + i++; + j--; + } + + calculateMinStackableVolume(head); + + // Successfully computed the next permutation + return head; + } + + public int length() { + return permutations.length; + } + + public PermutationRotationState getState() { + return new PermutationRotationState(rotations, permutations); + } + + public IndexedStackableItem getPermutation(int index) { + return stackableItems[permutations[index]]; + } + + protected int[] getRotations() { + return rotations; + } +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/IndexedStackableItem.java b/core/src/main/java/com/github/skjolber/packing/iterator/IndexedStackableItem.java new file mode 100644 index 00000000..1b0d720e --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/IndexedStackableItem.java @@ -0,0 +1,36 @@ +package com.github.skjolber.packing.iterator; + +import com.github.skjolber.packing.api.Stackable; +import com.github.skjolber.packing.api.StackableItem; + +/** + * + * Stackable item which fit within certain bounds, i.e. load dimensions of a container. + * + */ + +public class IndexedStackableItem extends StackableItem { + + private static final long serialVersionUID = 1L; + + protected int index; + + public IndexedStackableItem(Stackable stackable, int count, int index) { + super(stackable, count); + this.index = index; + } + + public boolean isEmpty() { + return count == 0; + } + + public int getIndex() { + return index; + } + + @Override + public IndexedStackableItem clone() { + return new IndexedStackableItem(stackable, count, index); + } + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/IndexedStackableItemGroup.java b/core/src/main/java/com/github/skjolber/packing/iterator/IndexedStackableItemGroup.java new file mode 100644 index 00000000..dc2b07dc --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/IndexedStackableItemGroup.java @@ -0,0 +1,89 @@ +package com.github.skjolber.packing.iterator; + +import java.util.ArrayList; +import java.util.List; + +import com.github.skjolber.packing.api.StackableItem; + +/** + * + * Items which belong together, for example different parts of a single product or order. + * + */ + +public class IndexedStackableItemGroup { + + private String id; + private List items; + + public IndexedStackableItemGroup(String id, List items) { + super(); + this.id = id; + this.items = items; + } + + public String getId() { + return id; + } + + public List getItems() { + return items; + } + + public void setId(String id) { + this.id = id; + } + + public void setItems(List items) { + this.items = items; + } + + public int size() { + return items.size(); + } + + public IndexedStackableItemGroup clone() { + List items = new ArrayList<>(); + + for (IndexedStackableItem stackableItem : this.items) { + items.add(stackableItem.clone()); + } + + return new IndexedStackableItemGroup(id, items); + } + + public IndexedStackableItem get(int k) { + return items.get(k); + } + + + public int stackableItemsCount() { + int count = 0; + for (StackableItem loadableItem : items) { + count += loadableItem.getCount(); + } + return count; + } + + public boolean isEmpty() { + for (StackableItem loadableItem : items) { + if(!loadableItem.isEmpty()) { + return false; + } + } + + return true; + } + + public void removeEmpty() { + for (int j = 0; j < items.size(); j++) { + StackableItem loadableItem = items.get(j); + + if(loadableItem.isEmpty()) { + items.remove(j); + j--; + } + } + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItem.java b/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItem.java new file mode 100644 index 00000000..f25db169 --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItem.java @@ -0,0 +1,18 @@ +package com.github.skjolber.packing.iterator; + +public class MutableIndexedStackableItem extends IndexedStackableItem { + + public final IndexedStackableItem source; + + public MutableIndexedStackableItem(IndexedStackableItem loadableItem) { + super(loadableItem.getStackable(), loadableItem.getCount(), loadableItem.getIndex()); + + this.source = loadableItem; + } + + public void reset() { + this.count = source.getCount(); + } + + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemGroupPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemGroupPermutationRotationIterator.java new file mode 100644 index 00000000..e84e5e8a --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemGroupPermutationRotationIterator.java @@ -0,0 +1,223 @@ +package com.github.skjolber.packing.iterator; + +import java.util.ArrayList; +import java.util.List; + +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.packager.StackableItems; + + /** + * + * An iterator which also acts as {@linkplain StackableItemsS}. + * + * State is restored on each remove, next rotation or next permutation. + * + */ + + +public class MutableIndexedStackableItemGroupPermutationRotationIterator extends AbstractStackableItemGroupPermutationRotationIterator implements StackableItems, StackableItemPermutationRotationIterator { + + public static Builder newBuilder() { + return new Builder(); + } + + public static DelegateBuilder newBuilder(AbstractStackableItemGroupIteratorBuilder builder) { + return new DelegateBuilder(builder); + } + + public static class Builder { + + private StackableItemGroupPermutationRotationIterator iterator; + + public Builder withIterator(StackableItemGroupPermutationRotationIterator iterator) { + this.iterator = iterator; + return this; + } + + public MutableIndexedStackableItemGroupPermutationRotationIterator build() { + return new MutableIndexedStackableItemGroupPermutationRotationIterator(iterator); + } + } + + public static class DelegateBuilder extends AbstractStackableItemGroupIteratorBuilder { + + private final AbstractStackableItemGroupIteratorBuilder builder; + + public DelegateBuilder(AbstractStackableItemGroupIteratorBuilder builder) { + this.builder = builder; + } + + public MutableIndexedStackableItemGroupPermutationRotationIterator build() { + if(maxLoadWeight == -1) { + throw new IllegalStateException(); + } + if(size == null) { + throw new IllegalStateException(); + } + if(builder == null) { + throw new IllegalStateException(); + } + + StackableItemGroupPermutationRotationIterator iterator = builder + .withLoadSize(size) + .withMaxLoadWeight(maxLoadWeight) + .withStackableItemGroups(stackableItemGroups) + .withFilter(filter) + .build(); + + return new MutableIndexedStackableItemGroupPermutationRotationIterator(iterator); + } + } + + protected List mutableStackableItems; + + protected final StackableItemGroupPermutationRotationIterator iterator; + + public MutableIndexedStackableItemGroupPermutationRotationIterator(StackableItemGroupPermutationRotationIterator iterator) { + super(iterator.getStackableItems(), iterator.getGroups()); + + permutations = new int[0]; // n! + + this.iterator = iterator; + + resetFromIterator(); + } + + protected void resetFromIterator() { + mutableStackableItems = new ArrayList<>(); + for (int i = 0; i < stackableItems.length; i++) { + IndexedStackableItem loadableItem = stackableItems[i]; + if(loadableItem != null && !loadableItem.isEmpty()) { + mutableStackableItems.add(new MutableIndexedStackableItem(loadableItem)); + } + } + + int[] permutations = iterator.getPermutations(); + + this.permutations = new int[permutations.length]; + this.rotations = new int[permutations.length]; + this.minStackableVolume = new long[permutations.length]; + this.groups = iterator.getGroups(); + + System.arraycopy(permutations, 0, this.permutations, 0, permutations.length); + System.arraycopy(iterator.getMinStackableVolume(), 0, minStackableVolume, 0, permutations.length); + } + + public IndexedStackableItem get(int index) { + return mutableStackableItems.get(index); + } + + @Override + public int size() { + return mutableStackableItems.size(); + } + + @Override + public int length() { + return permutations.length; + } + + @Override + public int nextPermutation(int maxIndex) { + int result = iterator.nextPermutation(maxIndex); + if(result != -1) { + resetFromIterator(); + } + return result; + } + + @Override + public int nextRotation(int maxIndex) { + int result = iterator.nextRotation(maxIndex); + if(result != -1) { + resetFromIterator(); + } + return result; + } + + @Override + public void remove(int index, int count) { + IndexedStackableItem loadableItem = mutableStackableItems.get(index); + loadableItem.decrement(count); + + if(loadableItem.isEmpty()) { + mutableStackableItems.remove(index); + } + + int remainingCount = permutations.length - count; + + // make inline changes, do not reset + int[] permutations = new int[remainingCount]; + int[] rotations = new int[remainingCount]; + + int offset = 0; + for(int i = 0; i < this.permutations.length; i++) { + if(this.permutations[i] == loadableItem.getIndex() && count > 0) { + count--; + } else { + permutations[offset] = this.permutations[i]; + rotations[offset] = this.rotations[i]; + + offset++; + } + } + + this.permutations = permutations; + this.rotations = rotations; + + if(remainingCount > 0) { + calculateMinStackableVolume(0); + } + } + + public PermutationRotationState getState() { + return new PermutationRotationState(rotations, permutations); + } + + @Override + public int[] getPermutations() { + int[] permutations = new int[this.permutations.length]; + System.arraycopy(this.permutations, 0, permutations, 0, permutations.length); + return permutations; + } + + public List get(PermutationRotationState state, int length) { + return iterator.get(state, length); + } + + @Override + public int nextRotation() { + int result = iterator.nextRotation(); + if(result != -1) { + resetFromIterator(); + } + return result; + } + + @Override + public int nextPermutation() { + int result = iterator.nextPermutation(); + if(result != -1) { + resetFromIterator(); + } + return result; + } + + @Override + public void removePermutations(List removed) { + iterator.removePermutations(removed); + + resetFromIterator(); + } + + public void removePermutations(int removed) { + iterator.removePermutations(removed); + + resetFromIterator(); + } + + protected StackableItemPermutationRotationIterator getIterator() { + return iterator; + } + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemPermutationRotationIterator.java new file mode 100644 index 00000000..e1ba92c6 --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/MutableIndexedStackableItemPermutationRotationIterator.java @@ -0,0 +1,222 @@ +package com.github.skjolber.packing.iterator; + +import java.util.ArrayList; +import java.util.List; + +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.packager.StackableItems; + + /** + * + * An iterator which also acts as {@linkplain StackableItemsS}. + * + * State is restored on each remove, next rotation or next permutation. + * + */ + + +public class MutableIndexedStackableItemPermutationRotationIterator extends AbstractStackableItemPermutationRotationIterator implements StackableItems, StackableItemPermutationRotationIterator { + + public static Builder newBuilder() { + return new Builder(); + } + + public static DelegateBuilder newBuilder(AbstractStackableItemIteratorBuilder builder) { + return new DelegateBuilder(builder); + } + + public static class Builder { + + private StackableItemPermutationRotationIterator iterator; + + public Builder withIterator(StackableItemPermutationRotationIterator iterator) { + this.iterator = iterator; + return this; + } + + public MutableIndexedStackableItemPermutationRotationIterator build() { + return new MutableIndexedStackableItemPermutationRotationIterator(iterator); + } + } + + public static class DelegateBuilder extends AbstractStackableItemIteratorBuilder { + + private final AbstractStackableItemIteratorBuilder builder; + + public DelegateBuilder(AbstractStackableItemIteratorBuilder builder) { + this.builder = builder; + } + + public MutableIndexedStackableItemPermutationRotationIterator build() { + if(maxLoadWeight == -1) { + throw new IllegalStateException(); + } + if(size == null) { + throw new IllegalStateException(); + } + if(builder == null) { + throw new IllegalStateException(); + } + + AbstractStackableItemPermutationRotationIterator iterator = builder + .withLoadSize(size) + .withMaxLoadWeight(maxLoadWeight) + .withStackableItems(stackableItems) + .withFilter(filter) + .build(); + + return new MutableIndexedStackableItemPermutationRotationIterator(iterator); + } + } + + protected List mutableStackableItems; + + protected final StackableItemPermutationRotationIterator iterator; + + public MutableIndexedStackableItemPermutationRotationIterator(StackableItemPermutationRotationIterator iterator) { + super(iterator.getStackableItems()); + + permutations = new int[0]; // n! + + this.iterator = iterator; + + resetFromIterator(); + } + + protected void resetFromIterator() { + mutableStackableItems = new ArrayList<>(); + for (int i = 0; i < stackableItems.length; i++) { + IndexedStackableItem loadableItem = stackableItems[i]; + if(loadableItem != null && !loadableItem.isEmpty()) { + mutableStackableItems.add(new MutableIndexedStackableItem(loadableItem)); + } + } + + int[] permutations = iterator.getPermutations(); + + this.permutations = new int[permutations.length]; + this.rotations = new int[permutations.length]; + this.minStackableVolume = new long[permutations.length]; + + System.arraycopy(permutations, 0, this.permutations, 0, permutations.length); + System.arraycopy(iterator.getMinStackableVolume(), 0, minStackableVolume, 0, permutations.length); + } + + public IndexedStackableItem get(int index) { + return mutableStackableItems.get(index); + } + + @Override + public int size() { + return mutableStackableItems.size(); + } + + @Override + public int length() { + return permutations.length; + } + + @Override + public int nextPermutation(int maxIndex) { + int result = iterator.nextPermutation(maxIndex); + if(result != -1) { + resetFromIterator(); + } + return result; + } + + @Override + public int nextRotation(int maxIndex) { + int result = iterator.nextRotation(maxIndex); + if(result != -1) { + resetFromIterator(); + } + return result; + } + + @Override + public void remove(int index, int count) { + IndexedStackableItem loadableItem = mutableStackableItems.get(index); + loadableItem.decrement(count); + + if(loadableItem.isEmpty()) { + mutableStackableItems.remove(index); + } + + int remainingCount = permutations.length - count; + + // make inline changes, do not reset + int[] permutations = new int[remainingCount]; + int[] rotations = new int[remainingCount]; + + int offset = 0; + for(int i = 0; i < this.permutations.length; i++) { + if(this.permutations[i] == loadableItem.getIndex() && count > 0) { + count--; + } else { + permutations[offset] = this.permutations[i]; + rotations[offset] = this.rotations[i]; + + offset++; + } + } + + this.permutations = permutations; + this.rotations = rotations; + + if(remainingCount > 0) { + calculateMinStackableVolume(0); + } + } + + public PermutationRotationState getState() { + return new PermutationRotationState(rotations, permutations); + } + + @Override + public int[] getPermutations() { + int[] permutations = new int[this.permutations.length]; + System.arraycopy(this.permutations, 0, permutations, 0, permutations.length); + return permutations; + } + + public List get(PermutationRotationState state, int length) { + return iterator.get(state, length); + } + + @Override + public int nextRotation() { + int result = iterator.nextRotation(); + if(result != -1) { + resetFromIterator(); + } + return result; + } + + @Override + public int nextPermutation() { + int result = iterator.nextPermutation(); + if(result != -1) { + resetFromIterator(); + } + return result; + } + + @Override + public void removePermutations(List removed) { + iterator.removePermutations(removed); + + resetFromIterator(); + } + + public void removePermutations(int removed) { + iterator.removePermutations(removed); + + resetFromIterator(); + } + + protected StackableItemPermutationRotationIterator getIterator() { + return iterator; + } + +} diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/ParallelPermutationRotationIteratorList.java b/core/src/main/java/com/github/skjolber/packing/iterator/ParallelPermutationRotationIteratorList.java index cfce2100..f871a8d4 100644 --- a/core/src/main/java/com/github/skjolber/packing/iterator/ParallelPermutationRotationIteratorList.java +++ b/core/src/main/java/com/github/skjolber/packing/iterator/ParallelPermutationRotationIteratorList.java @@ -185,6 +185,7 @@ private static int firstDuplicate(int[] frequencies) { return -1; } + // https://stemhash.com/efficient-permutations-in-lexicographic-order/ static int[] kthPermutation(int[] frequencies, int elementCount, long permutationCount, long rank) { int[] result = new int[PADDING + elementCount]; diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIterator.java b/core/src/main/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIterator.java new file mode 100644 index 00000000..f5048889 --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIterator.java @@ -0,0 +1,451 @@ +package com.github.skjolber.packing.iterator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.github.skjolber.packing.api.StackValue; +import com.github.skjolber.packing.api.StackableItem; + +public class ParallelStackableItemGroupPermutationRotationIterator extends AbstractStackableItemGroupPermutationRotationIterator { + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder extends AbstractStackableItemGroupIteratorBuilder { + + public ParallelStackableItemGroupPermutationRotationIterator build() { + if(maxLoadWeight == -1) { + throw new IllegalStateException(); + } + if(size == null) { + throw new IllegalStateException(); + } + + List groups = toMatrix(); + + List matrix = new ArrayList<>(); + for (IndexedStackableItemGroup loadableItemGroup : groups) { + matrix.addAll(loadableItemGroup.getItems()); + } + + ParallelStackableItemGroupPermutationRotationIterator result = new ParallelStackableItemGroupPermutationRotationIterator(matrix.toArray(new IndexedStackableItem[matrix.size()]), groups); + + result.initiatePermutations(); + + return result; + } + } + + // try to avoid false sharing by using padding + public long t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15 = -1L; + + private int[] lastPermutation; + private int lastPermutationMaxIndex = -1; + private boolean seenLastPermutationMaxIndex = false; + + public ParallelStackableItemGroupPermutationRotationIterator(IndexedStackableItem[] matrix, List groups) { + super(matrix, groups); + } + + public long preventOptmisation() { + return t0 + t1 + t2 + t3 + t4 + t5 + t6 + t7 + t8 + t9 + t10 + t11 + t12 + t13 + t14 + t15; + } + + public void setReset(int[] reset) { + this.reset = reset; + } + + public int[] getPermutations() { + int[] result = new int[permutations.length - ParallelPermutationRotationIteratorList.PADDING]; + System.arraycopy(permutations, ParallelPermutationRotationIteratorList.PADDING, result, 0, result.length); + return result; + } + + public void setGroups(List groups) { + this.groups = groups; + } + + public void setPermutations(int[] permutations) { + this.permutations = permutations; + } + + public void initMinStackableVolume() { + this.minStackableVolume = new long[permutations.length]; // i.e. with padding + + calculateMinStackableVolume(0); + } + + public void calculateMinStackableVolume(int offset) { + super.calculateMinStackableVolume(offset + ParallelPermutationRotationIteratorList.PADDING); + } + + public long getMinStackableVolume(int offset) { + return super.getMinStackableVolume(ParallelPermutationRotationIteratorList.PADDING + offset); + } + + public int[] getRotations() { + int[] result = new int[rotations.length - ParallelPermutationRotationIteratorList.PADDING]; + System.arraycopy(rotations, ParallelPermutationRotationIteratorList.PADDING, result, 0, result.length); + return result; + } + + public void setRotations(int[] rotations) { + this.rotations = rotations; + } + + public void setLastPermutation(int[] lastPermutation) { // array without padding + this.lastPermutation = lastPermutation; + + this.seenLastPermutationMaxIndex = false; + + // find the first item that differs, so that we do not have to + // compare items for each iteration (to detect whether we have done enough work) + for (int k = ParallelPermutationRotationIteratorList.PADDING; k < ParallelPermutationRotationIteratorList.PADDING + lastPermutation.length; k++) { + if(permutations[k] != lastPermutation[k]) { + lastPermutationMaxIndex = k; + + break; + } + } + } + + public int getLastPermutationMaxIndex() { + return lastPermutationMaxIndex; + } + + public int nextRotation() { + return nextRotation(rotations.length - 1 - ParallelPermutationRotationIteratorList.PADDING); + } + + public int nextRotation(int maxIndex) { + // next rotation + for (int i = ParallelPermutationRotationIteratorList.PADDING + maxIndex; i >= ParallelPermutationRotationIteratorList.PADDING; i--) { + if(rotations[i] < stackableItems[permutations[i]].getStackable().getStackValues().length - 1) { + rotations[i]++; + + // reset all following counters + System.arraycopy(reset, 0, rotations, i + 1, rotations.length - (i + 1)); + + return i - ParallelPermutationRotationIteratorList.PADDING; + } + } + + return -1; + } + + protected int nextPermutationImpl() { + // https://www.baeldung.com/cs/array-generate-all-permutations#permutations-in-lexicographic-order + + int[] permutations = this.permutations; + + int endIndex = permutations.length - 1; + + for(int g = groups.size() - 1; g >= 0; g--) { + + IndexedStackableItemGroup loadableItemGroup = groups.get(g); + + // Find longest non-increasing suffix + + int i = endIndex; + int startIndex = endIndex - loadableItemGroup.stackableItemsCount() + 1; + + while (i > startIndex && permutations[i - 1] >= permutations[i]) + i--; + // Now i is the head index of the suffix + + // Are we at the last permutation already? + if(i <= startIndex) { + + // reset current group + // TODO system arraycopy? + i = startIndex; + + for (StackableItem loadableItem : loadableItemGroup.getItems()) { + IndexedStackableItem indexedStackableItem = (IndexedStackableItem)loadableItem; + for(int k = 0; k < indexedStackableItem.getCount(); k++) { + permutations[i] = indexedStackableItem.getIndex(); + + i++; + } + } + + // skip to next group + endIndex = startIndex - 1; + + continue; + + } + + // Let array[i - 1] be the pivot + // Find rightmost element that exceeds the pivot + int j = endIndex; + while (permutations[j] <= permutations[i - 1]) + j--; + // Now the value array[j] will become the new pivot + // Assertion: j >= i + + int head = i - 1 - ParallelPermutationRotationIteratorList.PADDING; + + // Swap the pivot with j + int temp = permutations[i - 1]; + permutations[i - 1] = permutations[j]; + permutations[j] = temp; + + // Reverse the suffix + j = endIndex; + while (i < j) { + temp = permutations[i]; + permutations[i] = permutations[j]; + permutations[j] = temp; + i++; + j--; + } + + // Successfully computed the next permutation + return head; + } + return -1; + + } + + public int nextPermutation() { + resetRotations(); + + int resultIndex = nextPermutationImpl(); + + int result = returnPermuationWithinRangeOrMinusOne(resultIndex); + if(result != -1) { + calculateMinStackableVolume(resultIndex); + } + return result; + } + + public int nextPermutation(int maxIndex) { + // reset rotations + resetRotations(); + + int resultIndex = nextPermutationImpl(maxIndex); + + int result = returnPermuationWithinRangeOrMinusOne(resultIndex); + if(result != -1) { + calculateMinStackableVolume(resultIndex); + } + return result; + } + + private int returnPermuationWithinRangeOrMinusOne(int resultIndex) { + if(lastPermutation != null) { + if(resultIndex <= lastPermutationMaxIndex) { + // TODO initial check for bounds here + seenLastPermutationMaxIndex = true; + } + + if(seenLastPermutationMaxIndex) { + // are we still within our designated range? + // the next permutation must be lexicographically less than the first permutation + // in the next block + + // TODO is there a faster way to do this? + + int i = ParallelPermutationRotationIteratorList.PADDING; + while (i < lastPermutation.length) { + int value = permutations[i]; + if(value < lastPermutation[i]) { + return resultIndex; + } else if(value > lastPermutation[i]) { + return -1; + } + i++; + } + // so all most be equal + // we are at the exact last permutations + return -1; + } + } + + return resultIndex; + } + + public int nextPermutationImpl(int maxIndex) { + int limit = permutations.length; + + for(int g = groups.size() - 1; g >= 0; g--) { + IndexedStackableItemGroup loadableItemGroup = groups.get(g); + + // Find longest non-increasing suffix + int startIndex = limit - loadableItemGroup.stackableItemsCount(); + + if(startIndex <= ParallelPermutationRotationIteratorList.PADDING + maxIndex && ParallelPermutationRotationIteratorList.PADDING + maxIndex < limit) { + + while (ParallelPermutationRotationIteratorList.PADDING + maxIndex >= startIndex) { + + int current = permutations[ParallelPermutationRotationIteratorList.PADDING + maxIndex]; + + int minIndex = -1; + for (int i = ParallelPermutationRotationIteratorList.PADDING + maxIndex + 1; i < permutations.length; i++) { + if(current < permutations[i] && (minIndex == -1 || permutations[i] < permutations[minIndex])) { + minIndex = i; + } + } + + if(minIndex == -1) { + maxIndex--; + + continue; + } + + // swap indexes + permutations[ParallelPermutationRotationIteratorList.PADDING + maxIndex] = permutations[minIndex]; + permutations[minIndex] = current; + + Arrays.sort(permutations, ParallelPermutationRotationIteratorList.PADDING + maxIndex + 1, permutations.length); + + return maxIndex; + } + } + // reset current group + // TODO system arraycopy? + int i = startIndex; + + for (StackableItem loadableItem : loadableItemGroup.getItems()) { + IndexedStackableItem indexedStackableItem = (IndexedStackableItem)loadableItem; + for(int k = 0; k < indexedStackableItem.getCount(); k++) { + permutations[i] = indexedStackableItem.getIndex(); + + i++; + } + } + + // skip to next group + limit = startIndex; + } + + return -1; + } + + public StackValue get(int permutationIndex) { + return stackableItems[permutations[ParallelPermutationRotationIteratorList.PADDING + permutationIndex]].getStackable().getStackValue(rotations[ParallelPermutationRotationIteratorList.PADDING + permutationIndex]); + } + + @Override + public PermutationRotationState getState() { + return new PermutationRotationState(getRotations(), getPermutations()); + } + + public void resetRotations() { + System.arraycopy(reset, 0, rotations, ParallelPermutationRotationIteratorList.PADDING, rotations.length - ParallelPermutationRotationIteratorList.PADDING); + } + + @Override + public int length() { + return permutations.length - ParallelPermutationRotationIteratorList.PADDING; + } + + /** + * Remove permutations, if present. + */ + + @Override + public void removePermutations(List removed) { + for (Integer i : removed) { + IndexedStackableItem item = stackableItems[i]; + + item.decrement(); + + if(item.isEmpty()) { + stackableItems[i] = null; + } + } + + // go through all groups and clean up + for(int i = 0; i < groups.size(); i++) { + IndexedStackableItemGroup group = groups.get(i); + + group.removeEmpty(); + if(group.isEmpty()) { + groups.remove(i); + i--; + } + } + } + + @Override + public void removePermutations(int count) { + List removed = new ArrayList<>(permutations.length); + + for(int i = 0; i < count; i++) { + removed.add(permutations[ParallelPermutationRotationIteratorList.PADDING + i]); + } + + removePermutations(removed); + } + + @Override + public StackValue getStackValue(int index) { + return super.getStackValue(ParallelPermutationRotationIteratorList.PADDING + index); + } + + protected void initiatePermutations() { + int count = 0; + for (int j = 0; j < stackableItems.length; j++) { + IndexedStackableItem value = stackableItems[j]; + if(value != null && !value.isEmpty()) { + count += value.getCount(); + } + } + + // need to be in ascending order for the algorithm to work + int[] permutations = new int[ParallelPermutationRotationIteratorList.PADDING + count]; + + int offset = 0; + for (int j = 0; j < stackableItems.length; j++) { + IndexedStackableItem value = stackableItems[j]; + if(value != null && !value.isEmpty()) { + for (int k = 0; k < value.getCount(); k++) { + permutations[ParallelPermutationRotationIteratorList.PADDING + offset] = j; + offset++; + } + } + } + + initiatePermutation(permutations); + + int[] lastPermutation = new int[ParallelPermutationRotationIteratorList.PADDING + count]; + + count = 0; + for(int g = 0; g < groups.size(); g++) { + IndexedStackableItemGroup group = groups.get(g); + + int stackableItemsCount = group.stackableItemsCount(); + + for(int i = 0; i < stackableItemsCount; i++) { + lastPermutation[ParallelPermutationRotationIteratorList.PADDING + count + i] = permutations[ParallelPermutationRotationIteratorList.PADDING + count + stackableItemsCount - 1 - i]; + } + count += stackableItemsCount; + } + + setLastPermutation(lastPermutation); + + // include the last permutation + lastPermutationMaxIndex = -1; + } + + protected void initiatePermutation(int[] permutations) { + this.permutations = permutations; + this.rotations = new int[permutations.length]; + this.reset = new int[rotations.length]; + + if(permutations.length > ParallelPermutationRotationIteratorList.PADDING) { + initMinStackableVolume(); + } + + seenLastPermutationMaxIndex = true; + } + + public List getGroups() { + return groups; + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorList.java b/core/src/main/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorList.java new file mode 100644 index 00000000..129c33ee --- /dev/null +++ b/core/src/main/java/com/github/skjolber/packing/iterator/ParallelStackableItemGroupPermutationRotationIteratorList.java @@ -0,0 +1,332 @@ +package com.github.skjolber.packing.iterator; + +import java.util.ArrayList; +import java.util.List; + +import com.github.skjolber.packing.api.StackValue; + +/** + * + * This class is responsible for splitting the work load (as in the permutations) over multiple iterators. + * + */ + +public class ParallelStackableItemGroupPermutationRotationIteratorList implements StackableItemGroupPermutationRotationIterator { + + protected final static int PADDING = 16; + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder extends AbstractStackableItemGroupIteratorBuilder { + + private int parallelizationCount = -1; + + public Builder withParallelizationCount(int parallelizationCount) { + this.parallelizationCount = parallelizationCount; + + return this; + } + + public ParallelStackableItemGroupPermutationRotationIteratorList build() { + if(parallelizationCount == -1) { + throw new IllegalStateException(); + } + if(maxLoadWeight == -1) { + throw new IllegalStateException(); + } + if(size == null) { + throw new IllegalStateException(); + } + + List groups = toMatrix(); + + return new ParallelStackableItemGroupPermutationRotationIteratorList(groups, parallelizationCount); + } + } + + protected final int[] frequencies; + protected ParallelStackableItemGroupPermutationRotationIterator[] workUnits; + + protected int workUnitIndex = 0; + + public ParallelStackableItemGroupPermutationRotationIteratorList(List groups, int parallelizationCount) { + + workUnits = new ParallelStackableItemGroupPermutationRotationIterator[parallelizationCount]; + for (int i = 0; i < parallelizationCount; i++) { + + // clone working variables so threads are less of the same + // memory area as one another + + List clones = clone(groups); + + List matrix = new ArrayList<>(); + for (IndexedStackableItemGroup loadableItemGroup : clones) { + matrix.addAll(loadableItemGroup.getItems()); + } + + workUnits[i] = new ParallelStackableItemGroupPermutationRotationIterator(matrix.toArray(new IndexedStackableItem[matrix.size()]), clones); + if(workUnits[i].preventOptmisation() != -1L) { + throw new RuntimeException(); + } + } + + this.frequencies = workUnits[0].calculateFrequencies(); + + calculate(); + } + + private List clone(List groups) { + List result = new ArrayList<>(); + for (IndexedStackableItemGroup stackableItemGroup : groups) { + result.add(stackableItemGroup.clone()); + } + return result; + } + + private void calculate() { + int count = workUnits[0].getCount(); + List groups = workUnits[0].getGroups(); + + if(count == 0) { + return; + } + + int[] reset = new int[PADDING + count]; + + long permutationCount = workUnits[0].countPermutations(); + + if(permutationCount == -1L) { + throw new IllegalArgumentException(); + } + + int[] copyOfFrequencies = new int[frequencies.length]; + for (int i = 0; i < workUnits.length; i++) { + long permutationNumber = (permutationCount * i) / workUnits.length; + + permutationNumber++; + + System.arraycopy(frequencies, 0, copyOfFrequencies, 0, frequencies.length); + int[] permutations = unrank(copyOfFrequencies, count, permutationCount, permutationNumber, groups); + + workUnits[i].setPermutations(permutations); + workUnits[i].setRotations(new int[reset.length]); + workUnits[i].setReset(reset); + workUnits[i].initMinStackableVolume(); + } + + for (int i = 0; i < workUnits.length - 1; i++) { + int[] nextWorkUnitPermutations = workUnits[i + 1].getPermutations(); + int[] lexiographicalLimit = new int[PADDING + nextWorkUnitPermutations.length]; + + System.arraycopy(nextWorkUnitPermutations, 0, lexiographicalLimit, PADDING, nextWorkUnitPermutations.length); + + workUnits[i].setLastPermutation(lexiographicalLimit); + } + } + + protected static int[] unrank(int[] frequencies, int elementCount, long permutationCount, long rank, List groups) { + int[] result = new int[PADDING + elementCount]; + + int resultOffset = 0; + for (int j = 0; j < groups.size(); j++) { + IndexedStackableItemGroup group = groups.get(j); + + int stackableItemsCount = group.stackableItemsCount(); + + for(int i = 0; i < stackableItemsCount; i++) { + for(int k = 0; k < group.size(); k++) { + IndexedStackableItem item = (IndexedStackableItem)group.get(k); + + int index = item.getIndex(); + + if(frequencies[index] == 0) { + continue; + } + // suffixcount is the number of distinct perms that begin with x + long suffixcount = permutationCount * frequencies[index] / (stackableItemsCount - i); + if (rank <= suffixcount) { + result[PADDING + resultOffset + i] = index; + + permutationCount = suffixcount; + + frequencies[index]--; + break; + } + rank -= suffixcount; + } + } + + resultOffset += stackableItemsCount; + } + return result; + } + + + public ParallelStackableItemGroupPermutationRotationIterator[] getIterators() { + return workUnits; + } + + public ParallelStackableItemGroupPermutationRotationIterator getIterator(int i) { + return workUnits[i]; + } + + public int length() { + return workUnits[workUnitIndex].length(); + } + + @Override + public StackValue getStackValue(int index) { + return workUnits[workUnitIndex].getStackValue(index); + } + + @Override + public PermutationRotationState getState() { + return workUnits[workUnitIndex].getState(); + } + + @Override + public List get(PermutationRotationState state, int length) { + return workUnits[workUnitIndex].get(state, length); + } + + @Override + public long getMinStackableVolume(int index) { + return workUnits[workUnitIndex].getMinStackableVolume(index); + } + + @Override + public int getMinStackableAreaIndex(int index) { + return workUnits[workUnitIndex].getMinStackableAreaIndex(index); + } + + @Override + public int[] getPermutations() { + return workUnits[workUnitIndex].getPermutations(); + } + + @Override + public long countRotations() { + return workUnits[workUnitIndex].countRotations(); + } + + @Override + public int nextRotation() { + return workUnits[workUnitIndex].nextRotation(); + } + + @Override + public int nextRotation(int maxIndex) { + return workUnits[workUnitIndex].nextRotation(maxIndex); + } + + @Override + public int nextPermutation() { + while(workUnitIndex < workUnits.length) { + int nextPermutation = workUnits[workUnitIndex].nextPermutation(); + + if(nextPermutation != -1) { + return nextPermutation; + } + + // compare previous permutation to the next + workUnitIndex++; + if(workUnitIndex < workUnits.length) { + int[] permutations = workUnits[workUnitIndex].getPermutations(); + + // TODO how to find the correct index here? + for(int i = permutations.length - 2; i >= 0; i--) { + if(permutations[i] > permutations[i + 1]) { + return i; + } + } + + // should never happen + return 0; + } + } + return -1; + } + + @Override + public int nextPermutation(int maxIndex) { + + iterators: + while(workUnitIndex < workUnits.length) { + int nextPermutation = workUnits[workUnitIndex].nextPermutation(maxIndex); + + if(nextPermutation != -1) { + return nextPermutation; + } + // compare previous permutation to the next + workUnitIndex++; + if(workUnitIndex < workUnits.length) { + int[] permutations = workUnits[workUnitIndex].getPermutations(); + + // TODO how to find the correct index here? + for(int i = permutations.length - 2; i >= 0; i--) { + if(permutations[i] > permutations[i + 1]) { + if(i <= maxIndex) { + return i; + } else { + continue iterators; + } + } + } + + // should never happen + return 0; + } + } + return -1; + } + + @Override + public void removePermutations(int count) { + int[] permutations = workUnits[workUnitIndex].getPermutations(); + + List removed = new ArrayList<>(permutations.length); + + for(int i = 0; i < count; i++) { + removed.add(permutations[i]); + } + + removePermutations(removed); + } + + public void removePermutations(List removed) { + for (Integer integer : removed) { + if(frequencies[integer] > 0) { + frequencies[integer]--; + } + } + + for (ParallelStackableItemGroupPermutationRotationIterator unit : workUnits) { + unit.removePermutations(removed); + } + + calculate(); + } + + @Override + public long countPermutations() { + return workUnits[0].countPermutations(); + } + + @Override + public long[] getMinStackableVolume() { + return workUnits[workUnitIndex].getMinStackableVolume(); + } + + @Override + public IndexedStackableItem[] getStackableItems() { + return workUnits[workUnitIndex].getStackableItems(); + } + + @Override + public List 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(); + } + +}