# Java 11, \$n=17\$ in about 8.5 minutes ### Based on [Haskell solution by Christian Sievers](https://codegolf.stackexchange.com/a/199919) – upvote his! This answer is the result of learning enough Haskell to be able to understand Christian's answer, translating it into Java, applying numerous micro-optimizations, and throwing multiple cores at it. The exact runtime varies significantly depending on the number of cores available; this timing result is from my own two-core machine. A 48-core EC2 c5.24xlarge is able to compute \$n=17\$ in 16 seconds, and \$n=20\$ in 18 minutes. Parallelism can be disabled by adding the JVM argument `-Djava.util.concurrent.ForkJoinPool.common.parallelism=0`. Single-threaded performance is slightly better than double that of the Haskell solution. Some of the optimizations include: * Representing a point using a single int value * Using simplified hand-rolled collections based on int arrays, avoiding the primitive boxing required for the standard Java collections * Reimplementing polyomino enumeration based on [this paper](https://doi.org/10.1016%2F0012-365X%2881%2990237-5) -- my initial attempt at a direct translation of the Haskell code performed extra throwaway work that didn't actually contribute to the computation * Replacing higher-level [Stream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html)-based implementations with inlined code, making it very ugly and verbose The bulk of the processing time is spent in `Array.sort` calls in `normalizeInPlace`. Finding a way to compare polyomino transformations without sorting could easily result in a further 4x speedup. The forking is also not done very intelligently which leads to unbalanced tasks and unused cores at higher levels of parallelism. ```java import java.util.Arrays; import java.util.concurrent.RecursiveTask; import java.util.function.IntPredicate; import java.util.function.IntUnaryOperator; import java.util.function.LongSupplier; import java.util.function.ToLongFunction; /** * Utility methods for working with an int that represents a pair of short values. */ class Point { static final int start = p(0, 0); static final int[] neighbors = new int[] {-0x10000, -0x1, 0x1, 0x10000}; static int x(int p) { return (p >> 16) - 0x4000; } static int y(int p) { return (short)(p) - 0x4000; } static int p(int x, int y) { return ((x + 0x4000) << 16) | (y + 0x4000); } static int rot(int p) { return p(-y(p), x(p)); } static int mirror(int p) { return p(-x(p), y(p)); } } /** * Minimal primitive array-based collections. */ class IntArrays { /** Concatenates the end of the first array with the beginning of the second. */ static int[] arrayConcat(int[] a, int aOffset, int[] b, int bLen) { int aLength = a.length - aOffset; int[] result = new int[aLength + bLen]; System.arraycopy(a, aOffset, result, 0, aLength); System.arraycopy(b, 0, result, aLength, bLen); return result; } /** Adds a new value to a sorted set, returning the new result */ static int[] setAdd(int[] set, int val) { int[] dst = new int[set.length + 1]; int i = 0; for (; i < set.length && set[i] < val; i++) { dst[i] = set[i]; } dst[i] = val; for (; i < set.length; i++) { dst[i + 1] = set[i]; } return dst; } private static final int[] primes = new int[] { 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 }; /** * Allocate an array large enough to hold a fixed-capacity hash table * that can contain "seen" points for generating polyominos of size n. */ static int[] makeHashTable(int n) { return new int[primes[-(Arrays.binarySearch(primes, n * 3) + 1)]]; } /** Inserts a new value to a hash table, in-place */ static void hashInsert(int[] table, int val) { int pos = (val * 137) % table.length, startPos = pos; if (table[pos] != 0) { while ((table[pos = (pos + 1) % table.length]) != 0) { if (pos == startPos) { throw new AssertionError("table full"); } } } table[pos] = val; } /** Checks whether a hash table contains the specified value */ static boolean hashContains(int[] table, int val) { int pos = (val * 137) % table.length, startPos = pos; while (true) { int curr = table[pos]; if (curr == val) return true; if (curr == 0) return false; pos = (pos + 1) % table.length; if (pos == startPos) { throw new AssertionError("table full"); } } } } /** * Recursively generates int arrays representing collections of Points, * applying a function to each array to compute a long, and returns the sum * of all such values. */ class PolyominoVisitor extends RecursiveTask<Long> { PolyominoVisitor(ToLongFunction<? super int[]> func, int n) { this(func, n, 0, 1, new int[0], IntArrays.makeHashTable(n), new int[]{Point.start}); } private PolyominoVisitor(ToLongFunction<? super int[]> action, int n, int i, int limit, int[] used, int[] seen, int[] untried) { this.func = action; this.n = n; this.start = () -> visit(i, limit, used, seen, untried); } private final boolean visitSmaller = true; private final ToLongFunction<? super int[]> func; private final int n; private final LongSupplier start; @Override protected Long compute() { return start.getAsLong(); } private long visit(int i, int limit, int[] used, int[] seen, int[] untried) { long val = 0; if (used.length + 1 == n) { // reached the second to last level, so we can apply the function // directly to our children for (; i < limit; i++) { val += func.applyAsLong(IntArrays.setAdd(used, untried[i])); } } else if (used.length + 6 < n && limit - i >= 2) { // eligible to split PolyominoVisitor[] tasks = new PolyominoVisitor[limit - i]; for (int j = 0; j < tasks.length; j++) { tasks[j] = new PolyominoVisitor(func, n, i + j, i + j + 1, used, seen, untried); } invokeAll(tasks); for (PolyominoVisitor task : tasks) val += task.getRawResult(); return val; } else { // recursively visit children int[] newReachable = new int[4]; IntPredicate inSeen = p -> !IntArrays.hashContains(seen, p); for (; i < limit; i++) { int candidate = untried[i]; int[] child = IntArrays.setAdd(used, candidate); int reachableCount = neighbors(candidate, inSeen, newReachable); int[] newSeen = seen.clone(); for (int j = 0; j < reachableCount; j++) IntArrays.hashInsert(newSeen, newReachable[j]); int[] newUntried = IntArrays.arrayConcat(untried, i + 1, newReachable, reachableCount); val += visit(0, newUntried.length, child, newSeen, newUntried); } } if (visitSmaller && used.length > 0 && limit == untried.length) { val += func.applyAsLong(used); } return val; } /** * Write the greater-than-origin neighbors of the given point * that pass the provided predicate into the provided array, * returning the count written. */ private static int neighbors(int p, IntPredicate pred, int[] dst) { int count = 0; for (int offset : Point.neighbors) { int n = p + offset; if (n > Point.start && pred.test(n)) { dst[count++] = n; } } return count; } } /** * Function that computes how many buildings are constructable on a given * polyomino base. Considers symmetry, returning 0 if the figure is not the * canonical version (i.e. has a smaller transformation). * * Adapted largely unchanged from Christian Sievers * https://codegolf.stackexchange.com/a/199919 */ class BuildingCounter implements ToLongFunction<int[]> { private final int n; public BuildingCounter(int n) { this.n = n; } @Override public long applyAsLong(int[] fig) { return combinations(n - fig.length, fig); } private static int[] map(int[] fig, IntUnaryOperator func) { int[] result = new int[fig.length]; for (int i = 0; i < fig.length; i++) { result[i] = func.applyAsInt(fig[i]); } return result; } private static int[] normalizeInPlace(int[] fig) { Arrays.sort(fig); int d = fig[0] - Point.start; for (int i = 0; i < fig.length; i++) { fig[i] -= d; } return fig; } private static int[] rot(int[] ps) { return normalizeInPlace(map(ps, Point::rot)); } private static int[] mirror(int[] ps) { return normalizeInPlace(map(ps, Point::mirror)); } private static int myf(int r, int sz, int[] fig) { int max = Integer.MIN_VALUE; for (int p : fig) { if (p > max) max = p; } int w = Point.x(max); if (w % 2 == 0) { int wh = w / 2; int myb = 0; for (int p : fig) { if (Point.x(p) == wh) myb++; } return c12(myb, (sz - myb)/2, r); } else { return c1h(sz, r); } } private static int mdf(int r, int sz, int[] fig) { int lo = Integer.MAX_VALUE; for (int p : fig) { int tmp = Point.y(p); if (tmp < lo) lo = tmp; } int mdb = 0; for (int p : fig) { if (Point.x(p) == Point.y(p) - lo) mdb++; } return c12(mdb, (sz-mdb)/2, r); } private static long combinations(int r, int[] fig) { int[][] alts = new int[7][]; alts[0] = rot(fig); alts[1] = rot(alts[0]); alts[2] = rot(alts[1]); alts[3] = mirror(fig); alts[4] = mirror(alts[0]); alts[5] = mirror(alts[1]); alts[6] = mirror(alts[2]); int[] rfig = alts[0]; int[] cmps = new int[7]; for (int i = 0; i < 7; i++) { if ((cmps[i] = Arrays.compare(fig, alts[i])) > 0) { return 0; } } if (r == 0) { return 1; } int sz = fig.length; int qtfc = (sz % 2 == 0) ? c1q(sz, r) : sc1x(4, sz, r); int htfc = (sz % 2 == 0) ? c1h(sz, r) : sc1x(2, sz, r); int idfc = c1(sz, r); int[] fsc = new int[] {qtfc, htfc, qtfc, myf(r, sz, fig), mdf(r, sz, fig), myf(r, sz, rfig), mdf(r, sz, rfig)}; int gs = 1; int allfc = idfc; for (int i = 0; i < fsc.length; i++) { if (cmps[i] == 0) { allfc += fsc[i]; gs++; } } return allfc / gs; } private static int c1(int n, int t) { int v = 1; for (int x = 1; x <= t; x++) { v = v * (n+x-1) / x; } return v; } private static int c1h(int n, int t) { return c1d(n, t, 2); } private static int c1q(int n, int t) { return c1d(n, t, 4); } private static int c1d(int n, int t, int q) { if (t % q == 0) { return c1(n / q, t / q); } else { return 0; } } private static int sc1x(int m, int n, int t) { return c1(1 + n / m, t / m); } private static int c12(int s, int d, int t) { int sum = 0; for (int i = t/2; i >= 0; i--) { sum += c1(s, t-2*i) * c1(d, i); } return sum; } } public class Main { public static long count(int n) { return new PolyominoVisitor(new BuildingCounter(n), n).compute(); } public static void main(String[] args) { if (args.length > 0) { System.out.println(args[0] + ": " + count(Integer.parseInt(args[0]))); } else { for (int i = 1; i <= 99; i++) { System.out.println(i + ": " + count(i)); } } } } ``` ### Invocation ```bash javac Main.java java Main 17 ``` [Try it online!](https://tio.run/##tVp9b9s2Gv8/n4IrsEGqZcd20mZpXna9YsMV6G7Fsu4OKIyDLNE2E1lSRTm228tn7/0ekpJFmXKyDWeglU0@b3zeHyq34X3Yv43vvn4VyzwrSnaLhcGqFMngdVGEW3lxtLcRZWm0KgqeloNfOb5Jcc9/C@WdA3S2SqNSZOngbVq@L3gsorDkj8B9SMNi@0vOi7DMikOw77J0frPK80Twg3C/ZQT5k/l5cXR0/Pz5EXvOPgBKlFu25OUiiyWbZQVbZ8WdSOdsLcoFC1Mm0pKVi7BkBc8LLnFmyUKWh6Jg2YzJBbG8D5MVlwOQPD6KklBK9j4jvC9HDB9ZhqWI2EykYaLIYQFIVyz3hgEb@hdOqI8TlnIxX0yzQgI25Wuz@qU/3IyG@ASMvoGC@Y/WHnC4BjXitvHo/9w30tCn4OWqSJmXs@trNnrpsz7wT4GuRXnYo7HtpqE04Hv540RyRWQTaIouWt6G9QwRn11eKtH@y7ztbrWLdpGVnSLmXn8L@QJoIvc7KSxFUWTFISIbRWTbJPJQu9LPIhVLWC4vxFKUiAcWUvT0p6HkMYuyJOHK@Swvga/rGDP8QIu9QWwhRFL8k3A8zngak6fR15koZKkJa/@kxSmfizQllzVQkiM@4wGxsc8I51G4moVnVrQ9wl9mM8nLwMBN9er0HU@bylCQWJuD9xULB4n@2q/QL5qQIIOAWSVlw30r5J4iPdnB32xlyZcDJV@U5VsPctUyaTJw8qDi7h/AnCrACscgBPosF227ajDLKcgKr@OYwpzEVsHNygw/JVwd1jQyET6pnXROgOawLrUDAxS9@ofWLii3lIvdWDb1BdhKyT02mlj6ZQKAw90SZS/vAquXrIH23Xf066OYYBkMsd/rNbnSBzwJ4MpA7kg@HO2BEI3DLLtZqDMcZGOsAmjLJIiqewSEK0lSwPFWhrQ4vwjYWcBGyJGjE/yj7@cBG@P7GM8TrJ9g7RTPU6yd4vsLPF9g7@UosCi9xN4Z4M6wf4b97/H8Hs9zojkkBkPiMFQ/z4kn/RzTz5ORPs5F7WKaMjwtSTIKeKo1OrKTsJhT2Ger@YL8bpElMZxvJjY87kdhHkZUsxahxG44TXhFCVFfhkK55DPJefqM5VSFdFmb85TqKW3mWbLNliLNpKpg4jPcd2CIOHx3Gd7xf4DZb8RL5cfUkR8r7WtzfOx7OrENpoKK@Q0Pi2jh6c2ApZD2xCdv8CeTvdh7m0pelI7w2x2Z4qefJ2HEWyLfZyJWcJqIibgaxxVz0Ae5j4cdiDU6OfPZtxrD@HOgC/Z7BQbgRhDOmKcgP2J5wr5BOLb9fr0QCUdlq8GIFT3o8C1GE99JouKkkK9qYVxg9CkXRbZWqnstSQcoOj@q0vZM8WKzVZI8ayTC/SC0f@2@NY7ayAMNy71Z8OhO4szop3hhWazyTl3TZM4jMRPIpdq8thGnWZZwhANhvzFo/2dLGjOVxYq39UqEqdsFyk4BtvrIOhrkSotlgoLIdUMOa7hZmMgW4GFP2Sf6BOf4M47RdoNmy1O3/sm2Si9IxKpF0E1N3TFT1mm0QJR2VIMsA6ITooHfEghynGnRKd55GC1MRsSvKFvmK8qSLEErj7KOrkhrz3jUakm0QDlMEvwCrqsrN5nvdyEFZgvGN@i0UOmtKeaShoVro8A2imfPEpc/gBcmFZ0rr9UBtHtaSbJcCOnpvVR1JygWVcYcToJdIziws23q7@AmX5TOBsrED76zPv5BaUO1auQNnNnECgOhQRNqcatOcYX@NqjbHJ7W62lZIMDbWlBDGfWOZhSz9lIq4621aljyMGBcs3s6lwc5jAyau@ZbcXRqRrcMVWZRZG7QrydcxXUdpzb447Z2YSltujaa46qOVNMO/O2Xe14UIuYGKSsRKUiOhFB5vucouorGYI7mUhKo5z46BUyluL9uRE0Np7F6T0pBRKTRrFI6Stt56PgYsiOwcbjdqELxjfiETBzJBObM2BoFgxoiSg169DFGaFOLRQFdJSpHZKuCRUjkccFtuEabqo7t7FDpQ@fqXSlmA8XbKHYXn6aT1woz6kEv63fnTcaR3B0KeglpUmrPlUiYoQS7vmJjh8Z4IuaCMjTOKOE9pQXQjnlVJeVd1RLvbdfsWkVMKYk841aZFo9LTahu62/dSlNAH28nHQzrvNeZX2guuA30gzynK6yPutsVkd5ndxy9tKek8R1H20v@BMleafH9yvT0iyLq13D9q5rnvBYtE3rWGGRs7HD1XYVUAeh2z@q2BxwRGqoW78aZ05aZmvdoALiBjqiZoeT4zc5NrdZJ6zF3KeUpQaEaINRbERPLq4bXX7hgcRR1SkB2hE1NzHcS0BmC1PAmAy@lC3MR5tWogTl7YOnN75IIQEZTpIxBhCzGPQe0KwhscUwY2Ko204bhYsuEyDgk1getTUtbzVsao20dHiObdNCSzcHHuLVO/8OgwbHuiZW1AtYU/oNVSg/NBJTXrGqKhNbMc9dsuMtxV7XzmO22u3XlX6LoH7gvcAwj1VT8r0LAa6mEzKGskhf9chGm/axATk0bV6zm8myOaE314FxRULe/OfWPBIDqfI9KHePLLgqRmK09ZcCgImBfFUXKqdcQC72nPXe3rjlUI1G7vhpwAjv@SYZgd3HUnociEz/tSyLay9TdGvKf7iprPq7xR2eYnsHZHz5SmLnRnJLBSbIB5gEEhe9KKnQfpMTr9SZ219flacbUCmn//vWnenIga5meSbIFJp5lmG7ZdAUnhwkkbKOGUYmmL9KDD7BCbXiiVF@PMLq7HdCFrIRR4SJyu1zystg2L/@GpAB9NztfgbKQLM3ojQEnWkhXWQpTJQwNniTxPDEATeQMuk40IVMWYSphmGVIJ/BpYiHk13GYUxeo7oNQQXBCeO4cK7MiW2LYLoQsBbqkG8GJPOEsyjKXr46Poyzm8yyZkUGiO77RmAPo5Tg8Hp2fn4/OG3PR341yVBKhBneZJ3ypXnW0WmDT@n7pbnv1zmqawIFbdD3nXNTs@h@c/bAmphrPZlLQbg@9OzpjHJRuntS4CffsE1id8Ajl0PVide@V71iouLPeS6kktX99u3fjvWM8cUShvsJVRXgH6KzEmrC@g23mR8jlAZV60AMJ0nHN7Tx0Sl6YiM/8bfqertfcSq6KelYo3r59LU21jCQaTqD3Rlr4S8fXR2T9KxYfOCagHj@jeVdEd8fSdZPZVgE5Qi4DfZRXr4DuP8V96vdJf5aRpvAoL7bczpQyCz3Yyc9VRWhZTcGGG91p8DkvBj@//ed/fn/97sOPDtPkKA0tAvVdE9I9CPmGWu4yCJFYY1Pbf@MRvD0urtm3bGzuwfZLzppeMK3ZMRtf7O0tt1O7qD1F8IptJVDuE@81WhCQ6/UuDgwXVU4ZjT3ABsyTn@HZ@Oofj1EL/EcGgRp94ZFtir04PWDa@OmmTbKmZV//@w9bll50L/PaZvSic7/UEwQGhszX/PCzy/rLeNrRehxwLds6O0Ggb@IJmk1T7TcHZKJYm6iPL5aB3GpOzB3LrlzsFO5U9McJvTVNyuY7pzOs7cSiTcp@VyrV2AlS7Y2qPQPZ3h9b@6O9/RPaNwnGQf60sd3B4UUbZJ/JyzbIeOLvvd8Fd7rM00zau9Eyt5V0uAKcORM/eYVHlHTpM6WHGjw0cp4qzYo93cHQtOGKe@Mdw6eNM4UzKRkaoy53R064ahYxa/NTOaNLT0ocu6T3A9z1k0kJiAgZjTbeacDaOYLwF134ixb@2I0vYoUfjTzHLnm5jOw3qCRwoNgGSvj9yxuqOoXmRi4YqFzVXDiEUeyhqJUHW@o5ec/IXkPHrI5CJ3qkoZDRoYZCvYmp3KrjrZtmRgOpjJzXHXPZXTn2kpOmdgykxyo67KSv5HVSbmf6e1st9eE3ah2PSyRmPPcPTZj3GBO8tLfpj3wIszk0VT8u56JT0Dojxx62y4CN/cfJfXo6udMnkIstcvrxydImVTSE1KdDIQ9rpNDUJ7ClxxPL/fDpVV5FriqZ1auYA@f3RhjESZ6llmf5BD2MFXWpycYdXiVXy456TUFVHo8v9A01hVe/39YVYfd0hoFc/fFz4dP8O/KI3aHJBIiNad7Menos/TkUaTVq6nW7ZmOoPPj3CHs30bTYnknVKzZ/UL9lsbVpsVV/XLCEUN5NWYCE@nuquWw7FK01rr/amjJ/r5StygGMlZZJqjCoYeixZ6/YMzz02apmDpVOcprzDJzvP@aDlulGKh9esfPzziteh0yiLY3wH383/PXr19H4fw "Java (JDK) – Try It Online") ### Results (when run without an argument) ``` ... 16: 438030079 17: 2092403558 18: 10027947217 19: 48198234188 20: 232261124908 21: 1121853426115 ```