Search code examples
javaoptimizationprimessieve-algorithm

How do I generate Primes Using 6*k +- 1 rule


We know that all primes above 3 can be generated using:

6 * k + 1
6 * k - 1

However we all numbers generated from the above formulas are not prime.

For Example:    
6 * 6 - 1 = 35 which is clearly divisible by 5.

To Eliminate such conditions, I used a Sieve Method and removing the numbers which are factors of the numbers generated from the above formula.

Using the facts:

A number is said to be prime if it has no prime factors.

  1. As we can generate all the prime numbers using the above formulas.
  2. If we can remove all the multiples of the above numbers we are left with only prime numbers.

To generate primes below 1000.

ArrayList<Integer> primes = new ArrayList<>();
primes.add(2);//explicitly add
primes.add(3);//2 and 3
int n = 1000;
for (int i = 1; i <= (n / 6) ; i++) {
//get all the numbers which can be generated by the formula
    int prod6k = 6 * i;
    primes.add(prod6k - 1);
    primes.add(prod6k + 1);
}
for (int i = 0; i < primes.size(); i++) {
    int k = primes.get(i);
    //remove all the factors of the numbers generated by the formula
    for(int j = k * k; j <= n; j += k)//changed to k * k from 2 * k, Thanks to DTing
    {           
        int index = primes.indexOf(j); 
        if(index != -1)
            primes.remove(index);
    }
}
System.out.println(primes);

However, this method does generate the prime numbers correctly. This runs in a much faster way as we need not check for all the numbers which we do check in a Sieve.

My question is that am I missing any edge case? This would be a lot better but I never saw someone using this. Am I doing something wrong?

Can this approach be much more optimized?


Taking a boolean[] instead of an ArrayList is much faster.

int n = 100000000;
boolean[] primes = new boolean[n + 1];
for (int i = 0; i <= n; i++)
    primes[i] = false;
primes[2] = primes[3] = true;
for (int i = 1; i <= n / 6; i++) {
    int prod6k = 6 * i;
    primes[prod6k + 1] = true;
    primes[prod6k - 1] = true;
}
for (int i = 0; i <= n; i++) {
    if (primes[i]) {
        int k = i;
        for (int j = k * k; j <= n && j > 0; j += k) {
               primes[j] = false;
        }
      }
}
for (int i = 0; i <= n; i++)
    if (primes[i]) 
        System.out.print(i + " ");

Solution

  • You don't need to add all possible candidates to the array. You can create a Set to store all non primes.

    Also you can start checking at k * k, rather than 2 * k

      public void primesTo1000() {
        Set<Integer> notPrimes = new HashSet<>();
        ArrayList<Integer> primes = new ArrayList<>();
        primes.add(2);//explicitly add
        primes.add(3);//2 and 3
    
        for (int i = 1; i < (1000 / 6); i++) {
          handlePossiblePrime(6 * i - 1, primes, notPrimes);
          handlePossiblePrime(6 * i + 1, primes, notPrimes);
        }
        System.out.println(primes);
      }
    
      public void handlePossiblePrime(
          int k, List<Integer> primes, Set<Integer> notPrimes) {
        if (!notPrimes.contains(k)) {
          primes.add(k);
          for (int j = k * k; j <= 1000; j += k) {
            notPrimes.add(j);
          }
        }
      }
    

    untested code, check corners


    Here is a bit packing version of the sieve as suggested in the answer referenced by @Will Ness. Rather than return the nth prime, this version returns a list of primes to n:

    public List<Integer> primesTo(int n) {
      List<Integer> primes = new ArrayList<>();
      if (n > 1) {
        int limit = (n - 3) >> 1;
        int[] sieve = new int[(limit >> 5) + 1];
        for (int i = 0; i <= (int) (Math.sqrt(n) - 3) >> 1; i++)
          if ((sieve[i >> 5] & (1 << (i & 31))) == 0) {
            int p = i + i + 3;
            for (int j = (p * p - 3) >> 1; j <= limit; j += p)
              sieve[j >> 5] |= 1 << (j & 31);
          }
        primes.add(2);
        for (int i = 0; i <= limit; i++)
          if ((sieve[i >> 5] & (1 << (i & 31))) == 0)
            primes.add(i + i + 3);
      }
      return primes;
    }
    

    There seems to be a bug in your updated code that uses a boolean array (it is not returning all the primes).

    public static List<Integer> booleanSieve(int n) {
      boolean[] primes = new boolean[n + 5];
      for (int i = 0; i <= n; i++)
        primes[i] = false;
      primes[2] = primes[3] = true;
      for (int i = 1; i <= n / 6; i++) {
        int prod6k = 6 * i;
        primes[prod6k + 1] = true;
        primes[prod6k - 1] = true;
      }
      for (int i = 0; i <= n; i++) {
        if (primes[i]) {
          int k = i;
          for (int j = k * k; j <= n && j > 0; j += k) {
            primes[j] = false;
          }
        }
      }
    
      List<Integer> primesList = new ArrayList<>();
      for (int i = 0; i <= n; i++)
        if (primes[i])
          primesList.add(i);
    
      return primesList;
    }
    
    public static List<Integer> bitPacking(int n) {
      List<Integer> primes = new ArrayList<>();
      if (n > 1) {
        int limit = (n - 3) >> 1;
        int[] sieve = new int[(limit >> 5) + 1];
        for (int i = 0; i <= (int) (Math.sqrt(n) - 3) >> 1; i++)
          if ((sieve[i >> 5] & (1 << (i & 31))) == 0) {
            int p = i + i + 3;
            for (int j = (p * p - 3) >> 1; j <= limit; j += p)
              sieve[j >> 5] |= 1 << (j & 31);
          }
        primes.add(2);
        for (int i = 0; i <= limit; i++)
          if ((sieve[i >> 5] & (1 << (i & 31))) == 0)
            primes.add(i + i + 3);
      }
      return primes;
    }
    
    public static void main(String... args) {
      Executor executor = Executors.newSingleThreadExecutor();
      executor.execute(() -> {
        for (int i = 0; i < 10; i++) {
          int n = (int) Math.pow(10, i);
          Stopwatch timer = Stopwatch.createUnstarted();
          timer.start();
          List<Integer> result = booleanSieve(n);
          timer.stop();
          System.out.println(result.size() + "\tBoolean: " + timer);
        }
    
        for (int i = 0; i < 10; i++) {
          int n = (int) Math.pow(10, i);
          Stopwatch timer = Stopwatch.createUnstarted();
          timer.start();
          List<Integer> result = bitPacking(n);
          timer.stop();
          System.out.println(result.size() + "\tBitPacking: " + timer);
        }
      });
    }
    

    0   Boolean: 38.51 μs
    4   Boolean: 45.77 μs
    25  Boolean: 31.56 μs
    168 Boolean: 227.1 μs
    1229    Boolean: 1.395 ms
    9592    Boolean: 4.289 ms
    78491   Boolean: 25.96 ms
    664116  Boolean: 133.5 ms
    5717622 Boolean: 3.216 s
    46707218    Boolean: 32.18 s
    0   BitPacking: 117.0 μs
    4   BitPacking: 11.25 μs
    25  BitPacking: 11.53 μs
    168 BitPacking: 70.03 μs
    1229    BitPacking: 471.8 μs
    9592    BitPacking: 3.701 ms
    78498   BitPacking: 9.651 ms
    664579  BitPacking: 43.43 ms
    5761455 BitPacking: 1.483 s
    50847534    BitPacking: 17.71 s