/*
* This code demonstrates a solution to the problem described at
* https://stackoverflow.com/questions/61393463/
* Copyright (c) 2020, John McClane. All rights reserved.
*
*/
import java.util.*;
import java.util.function.Supplier;
import java.util.function.BiConsumer;
import java.math.BigInteger;
abstract class PartitionGenerator implements Supplier<int[]>{
protected final int numberCount;
protected final int min;
protected final int range;
protected final int sum; // shifted sum
protected final boolean sorted;
protected PartitionGenerator(int numberCount, int min, int max, int sum, boolean sorted) {
if (numberCount <= 0)
this.numberCount = numberCount;
this.min = min;
range = max - min;
if (range < 0)
sum -= numberCount * min;
if (sum < 0)
if (numberCount * range < sum)
this.sum = sum;
this.sorted = sorted;
}
// Whether this generator returns sorted arrays (i.e. combinations)
public final boolean isSorted() {
return sorted;
}
public interface GeneratorFactory {
PartitionGenerator create(int numberCount, int min, int max, int sum);
}
}
// Permutations with repetition (i.e. unsorted vectors) with given sum
class PermutationPartitionGenerator extends PartitionGenerator {
private final double[][] distributionTable;
public PermutationPartitionGenerator(int numberCount, int min, int max, int sum) {
super(numberCount, min, max, sum, false);
distributionTable = calculateSolutionCountTable();
}
private double[][] calculateSolutionCountTable() {
double[][] table = new double[numberCount + 1][sum + 1];
for (int i = 1; i <= sum; i++)
table[0][0] = 1.0;
for (int n = 1; n <= numberCount; n++) {
double[] t = table[n];
for (int s = 0; s <= sum; s++) {
z = z.add(a[i]);
b[s] = z;
t[s] = z.doubleValue();
}
// swap a and b
b = a;
a = c;
}
return table;
}
@Override
public int[] get() {
int[] p = new int[numberCount];
int s = sum; // current sum
for (int i = numberCount - 1; i >= 0; i--) {
double t = rand.nextDouble() * distributionTable[i + 1][s];
double[] tableRow = distributionTable[i];
int oldSum = s;
// lowerBound is introduced only for safety, it shouldn't be crossed
int lowerBound = s - range;
if (lowerBound < 0)
lowerBound = 0;
s++;
do
t -= tableRow[--s];
// s can be equal to lowerBound here with t > 0 only due to imprecise subtraction
while (t > 0 && s > lowerBound);
p[i] = min + (oldSum - s);
}
assert s == 0;
return p;
}
public static final PartitionGenerator.GeneratorFactory factory = (numberCount, min, max,sum) ->
new PermutationPartitionGenerator(numberCount, min, max, sum);
}
// Combinations with repetition (i.e. sorted vectors) with given sum
class CombinationPartitionGenerator extends PartitionGenerator {
private final double[][][] distributionTable;
public CombinationPartitionGenerator(int numberCount, int min, int max, int sum) {
super(numberCount, min, max, sum, true);
distributionTable = calculateSolutionCountTable();
}
private double[][][] calculateSolutionCountTable() {
double[][][] table = new double[numberCount + 1][range + 1][sum + 1];
double[][] t = table[0];
for (int m = 0; m <= range; m++) {
t[m][0] = 1.0;
for (int s = 1; s <= sum; s++) {
t[m][s] = 0.0;
}
}
for (int n = 1; n <= numberCount; n++) {
t = table[n];
for (int m = 0; m <= range; m++)
for (int s = 0; s <= sum; s++) {
BigInteger z;
if (m == 0)
z = a[0][s];
else {
z = b[m - 1][s];
if (m <= s)
z = z.add(a[m][s - m]);
}
b[m][s] = z;
t[m][s] = z.doubleValue();
}
// swap a and b
b = a;
a = c;
}
return table;
}
@Override
public int[] get() {
int[] p = new int[numberCount];
int m = range; // current max
int s = sum; // current sum
for (int i = numberCount - 1; i >= 0; i--) {
double t = rand.nextDouble() * distributionTable[i + 1][m][s];
double[][] tableCut = distributionTable[i];
if (s < m)
m = s;
s -= m;
while (true) {
t -= tableCut[m][s];
// m can be 0 here with t > 0 only due to imprecise subtraction
if (t <= 0 || m == 0)
break;
m--;
s++;
}
p[i] = min + m;
}
assert s == 0;
return p;
}
public static final PartitionGenerator.GeneratorFactory factory = (numberCount, min, max, sum) ->
new CombinationPartitionGenerator(numberCount, min, max, sum);
}
class SmithTromblePartitionGenerator extends PartitionGenerator {
public SmithTromblePartitionGenerator(int numberCount, int min, int max, int sum) {
super(numberCount, min, max, sum, false);
}
@Override
public int[] get() {
List<Integer> ls = new ArrayList<>(numberCount + 1);
int[] ret = new int[numberCount];
int increasedSum = sum + numberCount;
while (true) {
ls.add(0);
while (ls.size() < numberCount) {
int c = 1 + rand.nextInt(increasedSum - 1);
if (!ls.contains(c))
ls.add(c);
}
ls.add(increasedSum);
boolean good = true;
for (int i = 0; i < numberCount; i++) {
int x = ls.get(i + 1) - ls.get(i) - 1;
if (x > range) {
good = false;
break;
}
ret[i] = x;
}
if (good) {
for (int i = 0; i < numberCount; i++)
ret[i] += min;
return ret;
}
ls.clear();
}
}
public static final PartitionGenerator.GeneratorFactory factory = (numberCount, min, max, sum) ->
new SmithTromblePartitionGenerator(numberCount, min, max, sum);
}
// Enumerates all partitions with given parameters
class SequentialEnumerator extends PartitionGenerator {
private final int max;
private final int[] p;
private boolean finished;
public SequentialEnumerator(int numberCount, int min, int max, int sum, boolean sorted) {
super(numberCount, min, max, sum, sorted);
this.max = max;
p = new int[numberCount];
startOver();
}
private void startOver() {
finished = false;
int unshiftedSum = sum + numberCount * min;
}
private void fillMinimal(int beginIndex, int minValue, int fillSum) {
int fillRange = max - minValue;
if (fillRange == 0)
else {
int fillCount = numberCount - beginIndex;
fillSum -= fillCount * minValue;
int maxCount = fillSum / fillRange;
int maxStartIndex = numberCount - maxCount;
fillSum -= maxCount * fillRange;
if (fillSum != 0)
p[maxStartIndex - 1] = minValue + fillSum;
}
}
@Override
public int[] get() { // returns null when there is no more partition, then starts over
if (finished) {
startOver();
return null;
}
int[] pCopy = p.clone();
if (numberCount > 1) {
int i = numberCount;
int s = p[--i];
while (i > 0) {
int x = p[--i];
if (x == max) {
s += x;
continue;
}
x++;
s--;
int minRest = sorted ? x : min;
if (s < minRest * (numberCount - i - 1)) {
s += x;
continue;
}
p[i++]++;
fillMinimal(i, minRest, s);
return pCopy;
}
}
finished = true;
return pCopy;
}
public static final PartitionGenerator.GeneratorFactory permutationFactory = (numberCount, min, max, sum) ->
new SequentialEnumerator(numberCount, min, max, sum, false);
public static final PartitionGenerator.GeneratorFactory combinationFactory = (numberCount, min, max, sum) ->
new SequentialEnumerator(numberCount, min, max, sum, true);
}
class Test {
private final int numberCount;
private final int min;
private final int max;
private final int sum;
private final int repeatCount;
private final BiConsumer<PartitionGenerator, Test> procedure;
public Test(int numberCount, int min, int max, int sum, int repeatCount,
BiConsumer<PartitionGenerator, Test> procedure) {
this.numberCount = numberCount;
this.min = min;
this.max = max;
this.sum = sum;
this.repeatCount = repeatCount;
this.procedure = procedure;
}
@Override
numberCount, min, max, sum, repeatCount);
}
private static class GeneratedVector {
final int[] v;
GeneratedVector(int[] vect) {
v = vect;
}
@Override
public int hashCode() {
}
@Override
if (this == obj)
return true;
}
@Override
}
}
private static final Comparator<Map.Entry<GeneratedVector, Integer>> lexicographical = (e1, e2) -> {
int[] v1 = e1.getKey().v;
int[] v2 = e2.getKey().v;
int len = v1.length;
int d = len - v2.length;
if (d != 0)
return d;
for (int i = 0; i < len; i++) {
d = v1[i] - v2[i];
if (d != 0)
return d;
}
return 0;
};
.thenComparing(lexicographical);
public static int SHOW_MISSING_LIMIT = 10;
private static void checkMissingPartitions(Map<GeneratedVector, Integer> map, PartitionGenerator reference) {
int missingCount = 0;
while (true) {
int[] v = reference.get();
if (v == null)
break;
GeneratedVector gv = new GeneratedVector(v);
if (!map.containsKey(gv)) {
if (missingCount == 0)
if (++missingCount > SHOW_MISSING_LIMIT) {
break;
}
}
}
}
public static final BiConsumer<PartitionGenerator, Test> distributionTest(boolean sortByCount) {
return (PartitionGenerator gen, Test test) -> {
Map<GeneratedVector, Integer> combos = new HashMap<>();
// There's no point of checking permus for sorted generators
// because they are the same as combos for them
Map<GeneratedVector, Integer> permus = gen.isSorted() ? null : new HashMap<>();
for (int i = 0; i < test.repeatCount; i++) {
int[] v = gen.get();
if (v == null && gen instanceof SequentialEnumerator)
break;
if (permus != null) {
v = v.clone();
}
}
sortByCount ? byCount : lexicographical);
sortedEntries.addAll(combos.entrySet());
checkMissingPartitions(combos, test.getGenerator(SequentialEnumerator.combinationFactory));
if (permus != null) {
sortedEntries.clear();
sortedEntries.addAll(permus.entrySet());
checkMissingPartitions(permus, test.getGenerator(SequentialEnumerator.permutationFactory));
}
};
}
public static final BiConsumer<PartitionGenerator, Test> correctnessTest =
(PartitionGenerator gen, Test test) -> {
for (int i = 0; i < test.repeatCount; i++) {
int[] v = gen.get();
if (v == null && gen instanceof SequentialEnumerator)
v = gen.get();
if (v.length != test.numberCount)
int s = 0;
if (gen.isSorted()) {
if (v[0] < test.min || v[v.length - 1] > test.max)
int prev = test.min;
for (int x : v) {
if (x < prev)
s += x;
prev = x;
}
} else
for (int x : v) {
if (x < test.min || x > test.max)
s += x;
}
if (s != test.sum)
}
};
public static final BiConsumer<PartitionGenerator, Test> performanceTest =
(PartitionGenerator gen, Test test) -> {
for (int i = 0; i < test.repeatCount; i++)
gen.get();
System.out.format("%30s : %8.3f s %10.0f ns/test%n", getName(gen), time * 1e-9, time * 1.0 / test.repeatCount);
};
public PartitionGenerator getGenerator(PartitionGenerator.GeneratorFactory factory) {
return factory.create(numberCount, min, max, sum);
}
if (gen instanceof SequentialEnumerator)
return (gen.isSorted() ? "Sorted " : "Unsorted ") + name;
else
return name;
}
public static PartitionGenerator.GeneratorFactory[] factories = { SmithTromblePartitionGenerator.factory,
PermutationPartitionGenerator.factory, CombinationPartitionGenerator.factory,
SequentialEnumerator.permutationFactory, SequentialEnumerator.combinationFactory };
Test[] tests = {
new Test(3, 0, 3, 5, 3_000, distributionTest(false)),
new Test(3, 0, 6, 12, 3_000, distributionTest(true)),
new Test(50, -10, 20, 70, 1_000, correctnessTest),
new Test(7, 3, 10, 42, 25_000, performanceTest),
new Test(20, 3, 10, 120, 1_500, performanceTest)
};
for (Test t : tests) {
for (PartitionGenerator.GeneratorFactory factory : factories) {
PartitionGenerator candidate = t.getGenerator(factory);
t.procedure.accept(candidate, t);
}
}
}
}
Standard input is empty
=== 3 numbers from [0, 3] with sum 5, 3000 iterations === SmithTromblePartitionGenerator Combos (don't have to be uniform): [0, 2, 3]=1517 [1, 1, 3]=744 [1, 2, 2]=739 Permus: [0, 2, 3]=242 [0, 3, 2]=261 [1, 1, 3]=249 [1, 2, 2]=232 [1, 3, 1]=249 [2, 0, 3]=229 [2, 1, 2]=254 [2, 2, 1]=253 [2, 3, 0]=241 [3, 0, 2]=252 [3, 1, 1]=246 [3, 2, 0]=292 PermutationPartitionGenerator Combos (don't have to be uniform): [0, 2, 3]=1491 [1, 1, 3]=750 [1, 2, 2]=759 Permus: [0, 2, 3]=248 [0, 3, 2]=267 [1, 1, 3]=252 [1, 2, 2]=262 [1, 3, 1]=239 [2, 0, 3]=246 [2, 1, 2]=260 [2, 2, 1]=237 [2, 3, 0]=247 [3, 0, 2]=219 [3, 1, 1]=259 [3, 2, 0]=264 CombinationPartitionGenerator Combos: [0, 2, 3]=985 [1, 1, 3]=991 [1, 2, 2]=1024 Unsorted SequentialEnumerator Combos (don't have to be uniform): [0, 2, 3]=6 [1, 1, 3]=3 [1, 2, 2]=3 Permus: [0, 2, 3]=1 [0, 3, 2]=1 [1, 1, 3]=1 [1, 2, 2]=1 [1, 3, 1]=1 [2, 0, 3]=1 [2, 1, 2]=1 [2, 2, 1]=1 [2, 3, 0]=1 [3, 0, 2]=1 [3, 1, 1]=1 [3, 2, 0]=1 Sorted SequentialEnumerator Combos: [0, 2, 3]=1 [1, 1, 3]=1 [1, 2, 2]=1 === 3 numbers from [0, 6] with sum 12, 3000 iterations === SmithTromblePartitionGenerator Combos (don't have to be uniform): [4, 4, 4]=113 [3, 3, 6]=297 [2, 5, 5]=301 [0, 6, 6]=315 [2, 4, 6]=638 [1, 5, 6]=654 [3, 4, 5]=682 Permus: [6, 6, 0]=82 [3, 6, 3]=84 [6, 4, 2]=88 [6, 5, 1]=89 [5, 5, 2]=93 [2, 6, 4]=98 [4, 3, 5]=98 [5, 2, 5]=98 [3, 4, 5]=101 [6, 3, 3]=104 [3, 5, 4]=105 [5, 6, 1]=107 [6, 2, 4]=108 [1, 6, 5]=109 [3, 3, 6]=109 [2, 5, 5]=110 [6, 1, 5]=110 [4, 6, 2]=111 [4, 4, 4]=113 [4, 5, 3]=113 [6, 0, 6]=114 [2, 4, 6]=116 [4, 2, 6]=117 [0, 6, 6]=119 [1, 5, 6]=119 [5, 1, 6]=120 [5, 4, 3]=130 [5, 3, 4]=135 PermutationPartitionGenerator Combos (don't have to be uniform): [4, 4, 4]=104 [3, 3, 6]=313 [2, 5, 5]=322 [0, 6, 6]=347 [3, 4, 5]=610 [2, 4, 6]=647 [1, 5, 6]=657 Permus: [5, 3, 4]=90 [2, 6, 4]=93 [6, 2, 4]=93 [4, 3, 5]=95 [4, 5, 3]=99 [5, 6, 1]=99 [2, 5, 5]=100 [5, 5, 2]=100 [3, 4, 5]=102 [3, 3, 6]=103 [4, 4, 4]=104 [3, 6, 3]=105 [6, 3, 3]=105 [6, 1, 5]=106 [5, 1, 6]=109 [1, 6, 5]=110 [5, 4, 3]=110 [6, 4, 2]=110 [6, 5, 1]=111 [6, 0, 6]=113 [3, 5, 4]=114 [0, 6, 6]=115 [4, 6, 2]=115 [4, 2, 6]=117 [2, 4, 6]=119 [6, 6, 0]=119 [1, 5, 6]=122 [5, 2, 5]=122 CombinationPartitionGenerator Combos: [2, 5, 5]=405 [1, 5, 6]=406 [3, 4, 5]=408 [0, 6, 6]=429 [2, 4, 6]=437 [4, 4, 4]=450 [3, 3, 6]=465 Unsorted SequentialEnumerator Combos (don't have to be uniform): [4, 4, 4]=1 [0, 6, 6]=3 [2, 5, 5]=3 [3, 3, 6]=3 [1, 5, 6]=6 [2, 4, 6]=6 [3, 4, 5]=6 Permus: [0, 6, 6]=1 [1, 5, 6]=1 [1, 6, 5]=1 [2, 4, 6]=1 [2, 5, 5]=1 [2, 6, 4]=1 [3, 3, 6]=1 [3, 4, 5]=1 [3, 5, 4]=1 [3, 6, 3]=1 [4, 2, 6]=1 [4, 3, 5]=1 [4, 4, 4]=1 [4, 5, 3]=1 [4, 6, 2]=1 [5, 1, 6]=1 [5, 2, 5]=1 [5, 3, 4]=1 [5, 4, 3]=1 [5, 5, 2]=1 [5, 6, 1]=1 [6, 0, 6]=1 [6, 1, 5]=1 [6, 2, 4]=1 [6, 3, 3]=1 [6, 4, 2]=1 [6, 5, 1]=1 [6, 6, 0]=1 Sorted SequentialEnumerator Combos: [0, 6, 6]=1 [1, 5, 6]=1 [2, 4, 6]=1 [2, 5, 5]=1 [3, 3, 6]=1 [3, 4, 5]=1 [4, 4, 4]=1 === 50 numbers from [-10, 20] with sum 70, 1000 iterations === SmithTromblePartitionGenerator : correctness test passed PermutationPartitionGenerator : correctness test passed CombinationPartitionGenerator : correctness test passed Unsorted SequentialEnumerator : correctness test passed Sorted SequentialEnumerator : correctness test passed === 7 numbers from [3, 10] with sum 42, 25000 iterations === SmithTromblePartitionGenerator : 0.016 s 636 ns/test PermutationPartitionGenerator : 0.006 s 228 ns/test CombinationPartitionGenerator : 0.005 s 216 ns/test Unsorted SequentialEnumerator : 0.008 s 311 ns/test Sorted SequentialEnumerator : 0.045 s 1812 ns/test === 20 numbers from [3, 10] with sum 120, 1500 iterations === SmithTromblePartitionGenerator : 0.056 s 37466 ns/test PermutationPartitionGenerator : 0.001 s 644 ns/test CombinationPartitionGenerator : 0.001 s 470 ns/test Unsorted SequentialEnumerator : 0.000 s 30 ns/test Sorted SequentialEnumerator : 0.000 s 45 ns/test