Skip to content
188 changes: 188 additions & 0 deletions src/main/java/com/autonomouslogic/primes/Primorials.java
Original file line number Diff line number Diff line change
@@ -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;

/**
* <p>
* Functions for working with <a href="https://en.wikipedia.org/wiki/Primorial">primorials</a>.
* Each order <code>n</code> represents the primorial <code>p_n# = c#</code>, which is the sum of all primes between 2 and <code>c</code>.
* Each order consists of the order <code>n</code>, the last prime <code>c</code>, the product,
* and all the coprime offsets from <code>product</code>.
* </p>
*
* <p>
* See <a href="https://en.wikipedia.org/wiki/Primorial">Primorial</a>
* and <a href="https://en.wikipedia.org/wiki/Primality_test#Simple_methods">Primality test (Simple methods)</a>.
* </p>
*
* <p>
* The main function in this class summarises the orders with the output:
* <pre>
* 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
* </pre>
* </p>
*/
@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 <code>c</code>.
*/
long[] coprimeOffsets;

/**
* Returns all possible primes associated with this primorial order.
* Each number returned is in the form <code>product * k + i</code>, where <code>k</code> is an incrementing
* positive integer and <code>i</code> 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 <code>p_n#</code>
* @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);
}
}
}
25 changes: 22 additions & 3 deletions src/main/java/com/autonomouslogic/primes/TrialDivision.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
111 changes: 111 additions & 0 deletions src/test/java/com/autonomouslogic/primes/PrimorialsTest.java
Original file line number Diff line number Diff line change
@@ -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<Arguments> 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<Long> expected) {
assertEquals(
expected.toString(),
Primorials.ofOrderWithCoprimes(order)
.possiblePrimes()
.limit(expected.size())
.boxed()
.toList()
.toString());
}

public static Stream<Arguments> 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<Arguments> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}

Expand Down