diff --git a/src/main/java/com/autonomouslogic/primes/Primorials.java b/src/main/java/com/autonomouslogic/primes/Primorials.java new file mode 100644 index 0000000..5f858ff --- /dev/null +++ b/src/main/java/com/autonomouslogic/primes/Primorials.java @@ -0,0 +1,188 @@ +package com.autonomouslogic.primes; + +import java.util.Arrays; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Value; + +/** + *

+ * Functions for working with primorials. + * Each order n represents the primorial p_n# = c#, which is the sum of all primes between 2 and c. + * Each order consists of the order n, the last prime c, the product, + * and all the coprime offsets from product. + *

+ * + *

+ * See Primorial + * and Primality test (Simple methods). + *

+ * + *

+ * The main function in this class summarises the orders with the output: + *

+ * Order 0: c=1, product=1, 1 coprime offsets, 100.0% space
+ * Order 1: c=2, product=2, 1 coprime offsets, 50.0% space
+ * Order 2: c=3, product=6, 2 coprime offsets, 33.3% space
+ * Order 3: c=5, product=30, 8 coprime offsets, 26.7% space
+ * Order 4: c=7, product=210, 48 coprime offsets, 22.9% space
+ * Order 5: c=11, product=2310, 480 coprime offsets, 20.8% space
+ * Order 6: c=13, product=30030, 5760 coprime offsets, 19.2% space
+ * Order 7: c=17, product=510510, 92160 coprime offsets, 18.1% space
+ * Order 8: c=19, product=9699690, 1658880 coprime offsets, 17.1% space
+ * Order 9: c=23, product=223092870, 36495360 coprime offsets, 16.4% space
+ * 
+ *

+ */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Primorials { + /** + * The maximum order that will be cached and used in {@link #allPossiblePrimes(long)}. + */ + private static final int MAX_ORDER = 3; + + private static final Order[] ORDER_CACHE = new Order[MAX_ORDER + 1]; + + @Value + @AllArgsConstructor(access = AccessLevel.PROTECTED) + public static class Order { + /** + * The order of the primorial. + */ + int n; + + /** + * The last prime of the product. + */ + int c; + + /** + * The product of the primorial. + */ + long product; + + /** + * The coprime offsets which when added to multiples of the product are not divisible by any of the primes + * less than or equal to c. + */ + long[] coprimeOffsets; + + /** + * Returns all possible primes associated with this primorial order. + * Each number returned is in the form product * k + i, where k is an incrementing + * positive integer and i is each possible coprime offset. + * For instance, order 1 will return all odd numbers. + * Order 2 will first return 3 and 5, then 7 and 9, and so on. + * Order 3 will first return 31, 37, 41, 43, 47, 49, 53, 59, then 61, 67, 71, 73, 77, 79, 83, 89, and so on. + * Each order will return a higher start number, but the stream will contain fewer and fewer numbers. + * This can be used when searching for prime numbers. + * + * @return + */ + public LongStream possiblePrimes() { + return LongStream.iterate(1, i -> i + 1) + .flatMap(k -> Arrays.stream(coprimeOffsets).map(i -> k * this.product + i)); + } + } + + /** + * Returns the primorial p_n# + * @param n + * @return + */ + public static long ofOrder(int n) { + return Arrays.stream(PrimeList.PRIMES).limit(n).asLongStream().reduce(1, (left, right) -> left * right); + } + + /** + * Returns a primorial order with all the coprime offsets calculated. + * @param n + * @return + */ + public static Primorials.Order ofOrderWithCoprimes(int n) { + if (n <= MAX_ORDER && ORDER_CACHE[n] != null) { + return ORDER_CACHE[n]; + } + + var primes = Arrays.stream(PrimeList.PRIMES).limit(n).toArray(); + long product = 1; + for (int prime : primes) { + product *= prime; + } + final var finalProduct = product; + var offsets = LongStream.range(product, 2 * product) + .filter(num -> { + for (int prime : primes) { + if ((num % prime) == 0) { + return false; + } + } + return true; + }) + .map(num -> num - finalProduct) + .toArray(); + var c = primes.length == 0 ? 1 : primes[primes.length - 1]; + var order = new Primorials.Order(n, c, product, offsets); + if (n <= MAX_ORDER) { + ORDER_CACHE[n] = order; + } + return order; + } + + /** + * @see #allPossiblePrimes(long) + * @return + */ + public static LongStream allPossiblePrimes() { + return allPossiblePrimes(2); + } + + /** + * Returns all the possible primes from each primorial order in sequence, starting from the supplied number. + * The sequence will gradually contain fewer and fewer numbers as the orders are able to advance. + * @param from the number to start from, must be at least 2 + * @return + */ + public static LongStream allPossiblePrimes(long from) { + if (from < 2) { + throw new IllegalArgumentException("from must be at least 2"); + } + var mainStream = IntStream.rangeClosed(1, MAX_ORDER) + .mapToObj(Primorials::ofOrderWithCoprimes) + .filter(o -> ofOrder(o.getN() + 1) >= from) + .flatMapToLong(order -> { + System.out.printf("Order: %s%n", order); + var stream = order.possiblePrimes(); + if (order.getN() < MAX_ORDER) { + System.out.println("not MAX_ORDER"); + var nextK = ofOrder(order.getN() + 1); + System.out.printf("nextK: %s%n", nextK); + stream = order.possiblePrimes().takeWhile(num -> num < nextK); + } + if (order.getProduct() < from) { + stream = stream.filter(n -> n >= from); + } + System.out.println("return stream"); + return stream; + }); + if (from == 2) { + return LongStream.concat(LongStream.of(2), mainStream); + } else { + return mainStream; + } + } + + public static void main(String[] args) { + for (int i = 0; i <= 9; i++) { + var order = Primorials.ofOrderWithCoprimes(i); + var offsetsLen = order.getCoprimeOffsets().length; + var space = 100.0 * offsetsLen / order.getProduct(); + System.out.printf( + "Order %d: c=%d, product=%d, %d coprime offsets, %.1f%% space%n", + order.getN(), order.getC(), order.getProduct(), offsetsLen, space); + } + } +} diff --git a/src/main/java/com/autonomouslogic/primes/TrialDivision.java b/src/main/java/com/autonomouslogic/primes/TrialDivision.java index d00b3e3..3869c6e 100644 --- a/src/main/java/com/autonomouslogic/primes/TrialDivision.java +++ b/src/main/java/com/autonomouslogic/primes/TrialDivision.java @@ -8,15 +8,34 @@ public boolean isPrime(long number) { if (number == 2) { return true; } + var maxCheck = PrimeUtils.maxRequiredCheck(number); + + // This is so much faster than using the stream of possible primes below. It can probably be optimised. if (number % 2 == 0) { return false; } - var max = PrimeUtils.maxRequiredCheck(number); - for (int i = 3; i <= max; i += 2) { - if (number % i == 0) { + // for (int n = 3; n <= maxCheck; n += 2) { + // if (number % n == 0) { + // return false; + // } + // } + + var iterator = Primorials.allPossiblePrimes().iterator(); + while (iterator.hasNext()) { + var n = iterator.next(); + System.out.println("n=" + n); + if (n > maxCheck) { + break; + } + if (number % n == 0) { return false; } } + return true; + + // return Primorials.allPossiblePrimes() + // .takeWhile(n -> n <= maxCheck) + // .noneMatch(n -> number % n == 0); } } diff --git a/src/test/java/com/autonomouslogic/primes/PrimorialsTest.java b/src/test/java/com/autonomouslogic/primes/PrimorialsTest.java new file mode 100644 index 0000000..e144765 --- /dev/null +++ b/src/test/java/com/autonomouslogic/primes/PrimorialsTest.java @@ -0,0 +1,111 @@ +package com.autonomouslogic.primes; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class PrimorialsTest { + @ParameterizedTest + @MethodSource("orderTests") + void shouldCreateOrdersWithCoprimeOffsets(int order, Primorials.Order expected) { + assertEquals(expected, Primorials.ofOrderWithCoprimes(order)); + } + + public static Stream orderTests() { + return Stream.of( + Arguments.of(0, new Primorials.Order(0, 1, 1, new long[] {0})), + Arguments.of(1, new Primorials.Order(1, 2, 2, new long[] {1})), + Arguments.of(2, new Primorials.Order(2, 3, 6, new long[] {1, 5})), + Arguments.of(3, new Primorials.Order(3, 5, 30, new long[] {1, 7, 11, 13, 17, 19, 23, 29}))); + } + + @ParameterizedTest + @MethodSource("possiblePrimesTests") + void shouldReturnPossiblePrimes(int order, List expected) { + assertEquals( + expected.toString(), + Primorials.ofOrderWithCoprimes(order) + .possiblePrimes() + .limit(expected.size()) + .boxed() + .toList() + .toString()); + } + + public static Stream possiblePrimesTests() { + return Stream.of( + Arguments.of(0, List.of(1, 2, 3, 4, 5, 6, 7)), + Arguments.of(1, List.of(3, 5, 7, 9, 11, 13, 15)), + Arguments.of(2, List.of(7, 11, 13, 17)), + Arguments.of(3, List.of(31, 37, 41, 43, 47, 49, 53, 59, 61, 67, 71, 73, 77, 79, 83, 89))); + } + + @Test + void shouldReturnAllPossiblePrimes() { + var expected = Stream.of( + LongStream.of(2), + Primorials.ofOrderWithCoprimes(1).possiblePrimes().takeWhile(n -> n < 6), + Primorials.ofOrderWithCoprimes(2).possiblePrimes().takeWhile(n -> n < 30), + Primorials.ofOrderWithCoprimes(3).possiblePrimes().takeWhile(n -> n < 200)) + .flatMapToLong(s -> s) + .mapToObj(String::valueOf) + .toList(); + // .collect(Collectors.joining("\n")); + var actual = Primorials.allPossiblePrimes() + .peek(n -> System.out.println(n)) + // .takeWhile(n -> n <= 200) + .mapToObj(String::valueOf) + .limit(expected.size()) + .toList(); + // .count(); + // .collect(Collectors.joining("\n")); + assertEquals(expected, actual); + } + + @Test + void shouldReturnAllPossiblePrimesFromOffset() { + var expected = Stream.of( + Primorials.ofOrderWithCoprimes(2) + .possiblePrimes() + .filter(n -> n >= 15) + .takeWhile(n -> n < 30), + Primorials.ofOrderWithCoprimes(3).possiblePrimes().takeWhile(n -> n < 200)) + .flatMapToLong(s -> s) + .mapToObj(String::valueOf) + .collect(Collectors.joining("\n")); + var actual = Primorials.allPossiblePrimes(15) + .takeWhile(n -> n <= 200) + .mapToObj(String::valueOf) + .collect(Collectors.joining("\n")); + assertEquals(expected, actual); + } + + @ParameterizedTest + @MethodSource("primorialTests") + void shouldCalculatePrimorialOfOrder(int order, int expected) { + assertEquals(expected, Primorials.ofOrder(order)); + } + + public static Stream primorialTests() { + return Stream.of( + Arguments.of(0, 1), + Arguments.of(1, 2), + Arguments.of(2, 6), + Arguments.of(3, 30), + Arguments.of(4, 210), + Arguments.of(5, 2310)); + } + + @Test + void speedTest() { + var sum = Primorials.allPossiblePrimes().takeWhile(n -> n < 104729).sum(); + assertEquals(1058956954, sum); + } +} diff --git a/src/test/java/com/autonomouslogic/primes/TrialDivisionTest.java b/src/test/java/com/autonomouslogic/primes/TrialDivisionTest.java index 95f1a33..53a4628 100644 --- a/src/test/java/com/autonomouslogic/primes/TrialDivisionTest.java +++ b/src/test/java/com/autonomouslogic/primes/TrialDivisionTest.java @@ -13,6 +13,7 @@ public class TrialDivisionTest { void testTrialDivision() { var test = new TrialDivision(); PrimeTestUtil.primeTestNumbers() + .filter(t -> t.number == 49) .forEach(n -> assertEquals(n.isPrime, test.isPrime(n.number), Long.toString(n.number))); }