fork download
  1. /*
  2.  * This code demonstrates solution to the problem described at
  3.  * https://stackoverflow.com/questions/3492302/
  4.  * Copyright (c) 2020, John McClane. All rights reserved.
  5.  *
  6.  */
  7.  
  8. import java.util.*;
  9.  
  10. class MissingNumbers {
  11. private static class Interval {
  12. boolean ambiguous = true;
  13. final int begin;
  14. int quantity;
  15. long sum;
  16.  
  17. Interval(int begin, int end) { // begin inclusive, end exclusive
  18. this.begin = begin;
  19. quantity = end - begin;
  20. sum = quantity * ((long)end - 1 + begin) / 2;
  21. }
  22.  
  23. void exclude(int x) {
  24. quantity--;
  25. sum -= x;
  26. }
  27. }
  28.  
  29. public static int[] find(int minNumber, int maxNumber, NumberBag inputBag) {
  30. Interval full = new Interval(minNumber, ++maxNumber);
  31. for (inputBag.startOver(); inputBag.hasNext();)
  32. full.exclude(inputBag.next());
  33. int missingCount = full.quantity;
  34. if (missingCount == 0)
  35. return new int[0];
  36. Interval[] intervals = new Interval[missingCount];
  37. intervals[0] = full;
  38. int[] dividers = new int[missingCount];
  39. dividers[0] = minNumber;
  40. int intervalCount = 1;
  41. while (true) {
  42. int oldCount = intervalCount;
  43. for (int i = 0; i < oldCount; i++) {
  44. Interval itv = intervals[i];
  45. if (itv.ambiguous)
  46. if (itv.quantity == 1) // number inside itv uniquely identified
  47. itv.ambiguous = false;
  48. else
  49. intervalCount++; // itv will be split into two intervals
  50. }
  51. if (oldCount == intervalCount)
  52. break;
  53. int newIndex = intervalCount - 1;
  54. int end = maxNumber;
  55. for (int oldIndex = oldCount - 1; oldIndex >= 0; oldIndex--) {
  56. // newIndex always >= oldIndex
  57. Interval itv = intervals[oldIndex];
  58. int begin = itv.begin;
  59. if (itv.ambiguous) {
  60. // split interval itv
  61. // use floorDiv instead of / because input numbers can be negative
  62. int mean = (int)Math.floorDiv(itv.sum, itv.quantity) + 1;
  63. intervals[newIndex--] = new Interval(mean, end);
  64. intervals[newIndex--] = new Interval(begin, mean);
  65. } else
  66. intervals[newIndex--] = itv;
  67. end = begin;
  68. }
  69. for (int i = 0; i < intervalCount; i++)
  70. dividers[i] = intervals[i].begin;
  71. for (inputBag.startOver(); inputBag.hasNext();) {
  72. int x = inputBag.next();
  73. // find the interval to which x belongs
  74. int i = java.util.Arrays.binarySearch(dividers, 0, intervalCount, x);
  75. if (i < 0)
  76. i = -i - 2;
  77. Interval itv = intervals[i];
  78. if (itv.ambiguous)
  79. itv.exclude(x);
  80. }
  81. }
  82. assert intervalCount == missingCount;
  83. for (int i = 0; i < intervalCount; i++)
  84. dividers[i] = (int)intervals[i].sum;
  85. return dividers;
  86. }
  87. }
  88.  
  89. abstract class NumberBag {
  90. private int passCount;
  91.  
  92. public void startOver() {
  93. passCount++;
  94. }
  95.  
  96. public final int getPassCount() {
  97. return passCount;
  98. }
  99.  
  100. public abstract boolean hasNext();
  101.  
  102. public abstract int next();
  103.  
  104. // A lightweight version of Iterable<Integer> to avoid boxing of int
  105. public static NumberBag fromArray(int[] base, int fromIndex, int toIndex) {
  106. return new NumberBag() {
  107. int index = toIndex;
  108.  
  109. public void startOver() {
  110. super.startOver();
  111. index = fromIndex;
  112. }
  113.  
  114. public boolean hasNext() {
  115. return index < toIndex;
  116. }
  117.  
  118. public int next() {
  119. if (index >= toIndex)
  120. return base[index++];
  121. }
  122. };
  123. }
  124.  
  125. public static NumberBag fromArray(int[] base) {
  126. return fromArray(base, 0, base.length);
  127. }
  128.  
  129. public static NumberBag fromIterable(Iterable<Integer> base) {
  130. return new NumberBag() {
  131. Iterator<Integer> it;
  132.  
  133. public void startOver() {
  134. super.startOver();
  135. it = base.iterator();
  136. }
  137.  
  138. public boolean hasNext() {
  139. return it.hasNext();
  140. }
  141.  
  142. public int next() {
  143. return it.next();
  144. }
  145. };
  146. }
  147. }
  148.  
  149. class BatchTest {
  150. private static final Random rand = new Random();
  151. public static int MIN_NUMBER = 1;
  152. private final int minNumber = MIN_NUMBER;
  153. private final int numberCount;
  154. private final int[] numbers;
  155. private int missingCount;
  156. public long finderTime;
  157.  
  158. public BatchTest(int numberCount) {
  159. this.numberCount = numberCount;
  160. numbers = new int[numberCount];
  161. for (int i = 0; i < numberCount; i++)
  162. numbers[i] = minNumber + i;
  163. }
  164.  
  165. private int passBound() {
  166. int mBound = missingCount > 0 ? missingCount : 1;
  167. int nBound = 34 - Integer.numberOfLeadingZeros(numberCount - 1); // ceil(log_2(numberCount)) + 2
  168. return Math.min(mBound, nBound);
  169. }
  170.  
  171. private void error(String cause) {
  172. throw new RuntimeException("Error on '" + missingCount + " from " + numberCount + "' test, " + cause);
  173. }
  174.  
  175. // returns the number of times the input array was traversed in this test
  176. public int makeTest(int missingCount) {
  177. this.missingCount = missingCount;
  178. // numbers array is reused when numberCount stays the same,
  179. // just Fisher–Yates shuffle it for each test
  180. for (int i = numberCount - 1; i > 0; i--) {
  181. int j = rand.nextInt(i + 1);
  182. if (i != j) {
  183. int t = numbers[i];
  184. numbers[i] = numbers[j];
  185. numbers[j] = t;
  186. }
  187. }
  188. final int bagSize = numberCount - missingCount;
  189. NumberBag inputBag = NumberBag.fromArray(numbers, 0, bagSize);
  190. finderTime -= System.nanoTime();
  191. int[] found = MissingNumbers.find(minNumber, minNumber + numberCount - 1, inputBag);
  192. finderTime += System.nanoTime();
  193. if (inputBag.getPassCount() > passBound())
  194. error("too many passes (" + inputBag.getPassCount() + " while only " + passBound() + " allowed)");
  195. if (found.length != missingCount)
  196. error("wrong result length");
  197. int j = bagSize; // "missing" part beginning in numbers
  198. Arrays.sort(numbers, bagSize, numberCount);
  199. for (int i = 0; i < missingCount; i++)
  200. if (found[i] != numbers[j++])
  201. error("wrong result array, " + i + "-th element differs");
  202. return inputBag.getPassCount();
  203. }
  204.  
  205. public static void strideCheck(int numberCount, int minMissing, int maxMissing, int step, int repeats) {
  206. BatchTest t = new BatchTest(numberCount);
  207. System.out.println("╠═══════════════════════╬═════════════════╬═════════════════╣");
  208. for (int missingCount = minMissing; missingCount <= maxMissing; missingCount += step) {
  209. int minPass = Integer.MAX_VALUE;
  210. int passSum = 0;
  211. int maxPass = 0;
  212. t.finderTime = 0;
  213. for (int j = 1; j <= repeats; j++) {
  214. int pCount = t.makeTest(missingCount);
  215. if (pCount < minPass)
  216. minPass = pCount;
  217. passSum += pCount;
  218. if (pCount > maxPass)
  219. maxPass = pCount;
  220. }
  221. System.out.format("║ %9d %9d ║ %2d %5.2f %2d ║ %11.3f ║%n", missingCount, numberCount, minPass,
  222. (double)passSum / repeats, maxPass, t.finderTime * 1e-6 / repeats);
  223. }
  224. }
  225.  
  226. public static void main(String[] args) {
  227. System.out.println("\nSimple tests\n");
  228. int[] input = { 7, 1, 4, 9, 6, 2 };
  229. NumberBag bag = NumberBag.fromArray(input);
  230. int[] output = MissingNumbers.find(1, 10, bag);
  231. System.out.format("Input: %s%nMissing numbers: %s%nPass count: %d%n%n",
  232. Arrays.toString(input), Arrays.toString(output), bag.getPassCount());
  233.  
  234. List<Integer> inputList = new ArrayList<>();
  235. for (int i = 0; i < 10; i++)
  236. inputList.add(2 * i);
  237. Collections.shuffle(inputList);
  238. bag = NumberBag.fromIterable(inputList);
  239. output = MissingNumbers.find(0, 19, bag);
  240. System.out.format("Input: %s%nMissing numbers: %s%nPass count: %d%n%n",
  241. inputList, Arrays.toString(output), bag.getPassCount());
  242.  
  243. // Sieve of Eratosthenes
  244. final int MAXN = 1_000;
  245. List<Integer> nonPrimes = new ArrayList<>();
  246. nonPrimes.add(1);
  247. int[] primes;
  248. int lastPrimeIndex = 0;
  249. while (true) {
  250. primes = MissingNumbers.find(1, MAXN, NumberBag.fromIterable(nonPrimes));
  251. int p = primes[lastPrimeIndex]; // guaranteed to be prime
  252. int q = p;
  253. for (int i = lastPrimeIndex++; i < primes.length; i++) {
  254. q = primes[i]; // not necessarily prime
  255. int pq = p * q;
  256. if (pq > MAXN)
  257. break;
  258. nonPrimes.add(pq);
  259. }
  260. if (q == p)
  261. break;
  262. }
  263. System.out.format("Sieve of Eratosthenes. %d primes up to %d found:%n",
  264. primes.length, MAXN);
  265. for (int i = 0; i < primes.length; i++)
  266. System.out.format(" %4d%s", primes[i], (i % 10) < 9 ? "" : "\n");
  267.  
  268. System.out.println("\n\nAdvanced test");
  269. System.out.println("╔═══════════════════════╦═════════════════╦═════════════════╗");
  270. System.out.println("║ Number count ║ Passes ║ Average time ║");
  271. System.out.println("║ missimg total ║ min avg max ║ per search (ms) ║");
  272. long time = System.nanoTime();
  273. strideCheck(100, 0, 100, 1, 1_000);
  274. strideCheck(100_000, 1, 100_000, 11_111, 7);
  275. time = System.nanoTime() - time;
  276. System.out.println("╚═══════════════════════╩═════════════════╩═════════════════╝");
  277. System.out.format("Success. Advanced test time: %.2f s.%n", time * 1e-9);
  278. }
  279. }
  280.  
Success #stdin #stdout 4.54s 132676KB
stdin
Standard input is empty
stdout
Simple tests

Input: [7, 1, 4, 9, 6, 2]
Missing numbers: [3, 5, 8, 10]
Pass count: 3

Input: [0, 4, 14, 6, 8, 2, 18, 12, 10, 16]
Missing numbers: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
Pass count: 5

Sieve of Eratosthenes. 168 primes up to 1000 found:
    2    3    5    7   11   13   17   19   23   29
   31   37   41   43   47   53   59   61   67   71
   73   79   83   89   97  101  103  107  109  113
  127  131  137  139  149  151  157  163  167  173
  179  181  191  193  197  199  211  223  227  229
  233  239  241  251  257  263  269  271  277  281
  283  293  307  311  313  317  331  337  347  349
  353  359  367  373  379  383  389  397  401  409
  419  421  431  433  439  443  449  457  461  463
  467  479  487  491  499  503  509  521  523  541
  547  557  563  569  571  577  587  593  599  601
  607  613  617  619  631  641  643  647  653  659
  661  673  677  683  691  701  709  719  727  733
  739  743  751  757  761  769  773  787  797  809
  811  821  823  827  829  839  853  857  859  863
  877  881  883  887  907  911  919  929  937  941
  947  953  967  971  977  983  991  997

Advanced test
╔═══════════════════════╦═════════════════╦═════════════════╗
║      Number count     ║      Passes     ║  Average time   ║
║   missimg     total   ║  min  avg   max ║ per search (ms) ║
╠═══════════════════════╬═════════════════╬═════════════════╣
║         0        100  ║   1   1.00   1  ║        0.074    ║
║         1        100  ║   1   1.00   1  ║        0.000    ║
║         2        100  ║   2   2.00   2  ║        0.059    ║
║         3        100  ║   3   3.00   3  ║        0.002    ║
║         4        100  ║   3   3.31   4  ║        0.003    ║
║         5        100  ║   4   4.03   5  ║        0.004    ║
║         6        100  ║   4   4.15   6  ║        0.005    ║
║         7        100  ║   4   4.42   6  ║        0.005    ║
║         8        100  ║   4   4.77   6  ║        0.006    ║
║         9        100  ║   5   5.04   6  ║        0.006    ║
║        10        100  ║   5   5.10   6  ║        0.007    ║
║        11        100  ║   5   5.19   6  ║        0.007    ║
║        12        100  ║   5   5.34   6  ║        0.008    ║
║        13        100  ║   5   5.55   6  ║        0.011    ║
║        14        100  ║   5   5.73   7  ║        0.012    ║
║        15        100  ║   5   5.89   7  ║        0.012    ║
║        16        100  ║   5   6.00   7  ║        0.012    ║
║        17        100  ║   6   6.04   7  ║        0.012    ║
║        18        100  ║   6   6.05   7  ║        0.013    ║
║        19        100  ║   6   6.10   7  ║        0.009    ║
║        20        100  ║   6   6.13   7  ║        0.012    ║
║        21        100  ║   6   6.21   7  ║        0.009    ║
║        22        100  ║   6   6.32   7  ║        0.009    ║
║        23        100  ║   6   6.38   7  ║        0.009    ║
║        24        100  ║   6   6.51   7  ║        0.010    ║
║        25        100  ║   6   6.62   7  ║        0.010    ║
║        26        100  ║   6   6.73   7  ║        0.010    ║
║        27        100  ║   6   6.82   7  ║        0.011    ║
║        28        100  ║   6   6.87   7  ║        0.011    ║
║        29        100  ║   6   6.96   7  ║        0.011    ║
║        30        100  ║   6   6.99   7  ║        0.011    ║
║        31        100  ║   6   7.00   7  ║        0.011    ║
║        32        100  ║   6   7.00   8  ║        0.011    ║
║        33        100  ║   7   7.00   7  ║        0.011    ║
║        34        100  ║   7   7.00   8  ║        0.011    ║
║        35        100  ║   7   7.00   8  ║        0.011    ║
║        36        100  ║   7   7.01   8  ║        0.011    ║
║        37        100  ║   7   7.01   8  ║        0.011    ║
║        38        100  ║   7   7.01   8  ║        0.011    ║
║        39        100  ║   7   7.01   8  ║        0.011    ║
║        40        100  ║   7   7.02   8  ║        0.011    ║
║        41        100  ║   7   7.04   8  ║        0.011    ║
║        42        100  ║   7   7.05   8  ║        0.011    ║
║        43        100  ║   7   7.07   8  ║        0.012    ║
║        44        100  ║   7   7.08   8  ║        0.010    ║
║        45        100  ║   7   7.14   8  ║        0.010    ║
║        46        100  ║   7   7.15   8  ║        0.010    ║
║        47        100  ║   7   7.17   8  ║        0.010    ║
║        48        100  ║   7   7.23   8  ║        0.010    ║
║        49        100  ║   7   7.31   8  ║        0.010    ║
║        50        100  ║   7   7.36   8  ║        0.010    ║
║        51        100  ║   7   7.44   8  ║        0.010    ║
║        52        100  ║   7   7.53   8  ║        0.009    ║
║        53        100  ║   7   7.60   8  ║        0.009    ║
║        54        100  ║   7   7.66   8  ║        0.009    ║
║        55        100  ║   7   7.77   8  ║        0.009    ║
║        56        100  ║   7   7.81   8  ║        0.009    ║
║        57        100  ║   7   7.87   8  ║        0.009    ║
║        58        100  ║   7   7.91   8  ║        0.009    ║
║        59        100  ║   7   7.95   8  ║        0.009    ║
║        60        100  ║   7   7.98   8  ║        0.009    ║
║        61        100  ║   7   8.00   8  ║        0.010    ║
║        62        100  ║   7   8.00   8  ║        0.009    ║
║        63        100  ║   8   8.00   8  ║        0.009    ║
║        64        100  ║   7   8.00   8  ║        0.009    ║
║        65        100  ║   8   8.00   8  ║        0.008    ║
║        66        100  ║   8   8.00   8  ║        0.009    ║
║        67        100  ║   8   8.00   8  ║        0.012    ║
║        68        100  ║   8   8.00   8  ║        0.012    ║
║        69        100  ║   8   8.00   8  ║        0.011    ║
║        70        100  ║   8   8.00   8  ║        0.011    ║
║        71        100  ║   8   8.00   8  ║        0.011    ║
║        72        100  ║   8   8.00   8  ║        0.011    ║
║        73        100  ║   8   8.00   8  ║        0.008    ║
║        74        100  ║   8   8.00   8  ║        0.010    ║
║        75        100  ║   8   8.00   8  ║        0.009    ║
║        76        100  ║   8   8.00   8  ║        0.007    ║
║        77        100  ║   8   8.00   8  ║        0.007    ║
║        78        100  ║   8   8.00   8  ║        0.007    ║
║        79        100  ║   8   8.00   8  ║        0.007    ║
║        80        100  ║   8   8.00   8  ║        0.006    ║
║        81        100  ║   8   8.00   8  ║        0.006    ║
║        82        100  ║   8   8.00   8  ║        0.009    ║
║        83        100  ║   8   8.00   8  ║        0.009    ║
║        84        100  ║   8   8.00   8  ║        0.008    ║
║        85        100  ║   8   8.00   8  ║        0.008    ║
║        86        100  ║   8   8.00   8  ║        0.008    ║
║        87        100  ║   8   8.00   8  ║        0.009    ║
║        88        100  ║   8   8.00   8  ║        0.008    ║
║        89        100  ║   8   8.00   8  ║        0.005    ║
║        90        100  ║   8   8.00   8  ║        0.006    ║
║        91        100  ║   8   8.00   8  ║        0.006    ║
║        92        100  ║   8   8.00   8  ║        0.004    ║
║        93        100  ║   8   8.00   8  ║        0.004    ║
║        94        100  ║   8   8.00   8  ║        0.004    ║
║        95        100  ║   8   8.00   8  ║        0.004    ║
║        96        100  ║   8   8.00   8  ║        0.004    ║
║        97        100  ║   8   8.00   8  ║        0.003    ║
║        98        100  ║   8   8.00   8  ║        0.004    ║
║        99        100  ║   8   8.00   8  ║        0.003    ║
║       100        100  ║   8   8.00   8  ║        0.003    ║
╠═══════════════════════╬═════════════════╬═════════════════╣
║         1     100000  ║   1   1.00   1  ║        0.193    ║
║     11112     100000  ║  16  16.29  17  ║       60.046    ║
║     22223     100000  ║  17  17.00  17  ║       62.035    ║
║     33334     100000  ║  17  17.57  18  ║       59.493    ║
║     44445     100000  ║  18  18.00  18  ║       50.021    ║
║     55556     100000  ║  18  18.00  18  ║       43.773    ║
║     66667     100000  ║  18  18.00  18  ║       34.680    ║
║     77778     100000  ║  18  18.00  18  ║       23.273    ║
║     88889     100000  ║  18  18.00  18  ║       12.888    ║
║    100000     100000  ║  18  18.00  18  ║        3.146    ║
╚═══════════════════════╩═════════════════╩═════════════════╝
Success. Advanced test time: 4.34 s.