/* * 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