在Java中获取素数的无限列表

一个常见的问题是确定数字的素因式分解。 蛮力方法是审判部门( 维基百科可汗学院 ),但是如果必须考虑多个数字,这需要大量的浪费工作。

一种广泛使用的解决方案是Eratosthenes筛( 维基百科数学世界 )。 容易修改Eratosthenes的筛网以使其包含每个复合数的最大素数。 这使得随后计算数字的素因式分解非常便宜。

如果我们只关心素数,则可以使用带有Eratosthenes筛子的位图,也可以使用Atkin筛子( )。

(旁注:为清楚起见,我忽略了素数始终为“ 1 mod 2,n> 2”和“ 1或5 mod 6,n> 5”这一事实所引起的常见优化。这可以大大减少筛子所需的内存量。)

public enum SieveOfEratosthenes {
    SIEVE;
    
    private int[] sieve;

    private SieveOfEratosthenes() {
        // initialize with first million primes - 15485865
        // initialize with first 10k primes - 104729
        sieve = initialize(104729);
    }

    /**
     * Initialize the sieve.
     */
    private int[] initialize(int sieveSize) {
        long sqrt = Math.round(Math.ceil(Math.sqrt(sieveSize)));
        long actualSieveSize = (int) (sqrt * sqrt);

        // data is initialized to zero
        int[] sieve = new int[actualSieveSize];

        for (int x = 2; x < sqrt; x++) {
            if (sieve[x] == 0) {
                for (int y = 2 * x; y < actualSieveSize; y += x) {
                    sieve[y] = x;
                }
            }
        }

        return sieve;
    }

    /**
     * Is this a prime number?
     *
     * @FIXME handle n >= sieve.length!
     * 
     * @param n
     * @return true if prime
     * @throws IllegalArgumentException
     *             if negative number
     */
    public boolean isPrime(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("value must be non-zero");
        }

        boolean isPrime = sieve[n] == 0;

        return isPrime;
    }

    /**
     * Factorize a number
     *
     * @FIXME handle n >= sieve.length!
     * 
     * @param n
     * @return map of prime divisors (key) and exponent(value)
     * @throws IllegalArgumentException
     *             if negative number
     */
    private Map<Integer, Integer> factorize(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("value must be non-zero");
        }

        final Map<Integer, Integer> factors = new TreeMap<Integer, Integer>();

        for (int factor = sieve[n]; factor > 0; factor = sieve[n]) {
            if (factors.containsKey(factor)) {
                factors.put(factor, 1 + factors.get(factor));
            } else {
                factors.put(factor, 1);
            }

            n /= factor;
        }

        // must add final term
        if (factors.containsKey(n)) {
            factors.put(n, 1 + factors.get(n));
        } else {
            factors.put(n, 1);
        }

        return factors;
    }

    /**
     * Convert a factorization to a human-friendly string. The format is a
     * comma-delimited list where each element is either a prime number p (as
     * "p"), or the nth power of a prime number as "p^n".
     * 
     * @param factors
     *            factorization
     * @return string representation of factorization.
     * @throws IllegalArgumentException
     *             if negative number
     */
    public String toString(Map factors) {
        StringBuilder sb = new StringBuilder(20);

        for (Map.Entry entry : factors.entrySet()) {
            sb.append(", ");

            if (entry.getValue() == 1) {
                sb.append(String.valueOf(entry.getKey()));
            } else {
                sb.append(String.valueOf(entry.getKey()));
                sb.append("^");
                sb.append(String.valueOf(entry.getValue()));
            }
        }

        return sb.substring(2);
    }
}

该代码有一个主要弱点-如果请求的数字超出范围,它将失败。 有一个简单的解决方法–我们可以根据需要动态调整筛子的大小。 我们使用Lock来确保多线程调用不会使筛选器处于中间状态。 我们需要注意避免在读锁和写锁之间陷入僵局。

private final ReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * Initialize the sieve. This method is called when it is necessary to grow
     * the sieve.
     */
    private void reinitialize(int n) {
        try {
            lock.writeLock().lock();
            // allocate 50% more than required to minimize thrashing.
            initialize((3 * n) / 2);
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * Is this a prime number?
     * 
     * @param n
     * @return true if prime
     * @throws IllegalArgumentException
     *             if negative number
     */
    public boolean isPrime(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("value must be non-zero");
        }

        if (n > sieve.length) {
            reinitialize(n);
        }

        boolean isPrime = false;
        try {
            lock.readLock().lock();
            isPrime = sieve[n] == 0;
        } finally {
            lock.readLock().unlock();
        }

        return isPrime;
    }

    /**
     * Factorize a number
     * 
     * @param n
     * @return map of prime divisors (key) and exponent(value)
     * @throws IllegalArgumentException
     *             if negative number
     */
    private Map<Integer, Integer> factorize(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("value must be non-zero");
        }

        final Map<Integer, Integer> factors = new TreeMap<Integer, Integer>();

        try {
            if (n > sieve.length) {
                reinitialize(n);
            }

            lock.readLock().lock();
            for (int factor = sieve[n]; factor > 0; factor = sieve[n]) {
                if (factors.containsKey(factor)) {
                    factors.put(factor, 1 + factors.get(factor));
                } else {
                    factors.put(factor, 1);
                }

                n /= factor;
            }
        } finally {
            lock.readLock().unlock();
        }

        // must add final term
        if (factors.containsKey(n)) {
            factors.put(n, 1 + factors.get(n));
        } else {
            factors.put(n, 1);
        }

        return factors;
    }

Iterable <Integer>和foreach循环

在现实世界中,使用foreach循环(或显式Iterator)通常比逐项探查表要容易得多。 幸运的是,创建一个迭代器很容易,该迭代器建立在我们的自增长筛子上。

/**
     * @see java.util.List#get(int)
     *
     * We can use a cache of the first few (1000? 10,000?) primes
     * for improved performance.
     *
     * @param n
     * @return nth prime (starting with 2)
     * @throws IllegalArgumentException
     *             if negative number
     */
    public Integer get(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("value must be non-zero");
        }

        Iterator<Integer> iter = iterator();
        for (int i = 0; i < n; i++) {
            iter.next();
        }

        return iter.next();
    }

    /**
     * @see java.util.List#indexOf(java.lang.Object)
     */
    public int indexOf(Integer n) {
        if (!isPrime(n)) {
            return -1;
        }

        int index = 0;
        for (int i : sieve) {
            if (i == n) {
                return index;
            }
            index++;
        }
        return -1;
    }
   /**
     * @see java.lang.Iterable#iterator()
     */
    public Iterator<Integer> iterator() {
        return new EratosthenesListIterator();
    }

    public ListIterator<Integer> listIterator() {
        return new EratosthenesListIterator();
    }

    /**
     * List iterator.
     *
     * @author Bear Giles <bgiles@coyotesong.com>
     */
    static class EratosthenesListIterator extends AbstractListIterator<Integer> {
        int offset = 2;

        /**
         * @see com.invariantproperties.projecteuler.AbstractListIterator#getNext()
         */
        @Override
        protected Integer getNext() {
            while (true) {
                offset++;
                if (SIEVE.isPrime(offset)) {
                    return offset;
                }
            }
 
            // we'll always find a value since we dynamically resize the sieve.
        }

        /**
         * @see com.invariantproperties.projecteuler.AbstractListIterator#getPrevious()
         */
        @Override
        protected Integer getPrevious() {
            while (offset > 0) {
                offset--;
                if (SIEVE.isPrime(offset)) {
                    return offset;
                }
            }

            // we only get here if something went horribly wrong
            throw new NoSuchElementException();
        }
    }
}

重要提示:代码:

for (int prime : SieveOfEratosthenes.SIEVE) { ... }

本质上是一个无限循环。 仅当JVM在分配新筛子时用完堆空间时,它才会停止。

实际上,这意味着我们可以在筛子中保持的最大质数约为1 GB。 这需要4 GB和4字节的整数。 如果我们只关心素数,并使用常见的优化方法,那么4 GB可以保存有关64 GB值的信息。 为简单起见,我们可以将其称为9到10位数字(以10为基数)。

如果将筛子放在磁盘上怎么办?

没有理由将筛子保留在内存中。 我们的迭代器可以从磁盘而不是内存缓存中安静地加载值。 一个4 TB的磁盘(可能是在原始模式下访问的)似乎将我们的筛子的大小提高到14到15位数字(以10为基数)。 实际上,它会少一些,因为我们必须将原始类型的大小从intlong增大一倍,然后再可能是更大的格式。

更多!

通过注意我们只需要计算sqrt(n)即可初始化n个值的筛子,从而可以大大增加筛子的有效尺寸。 我们可以反过来说,可以使用完全填充的n个值的筛子填充另一个n 2个值的筛子。 在这种情况下,我们只希望填充一个波段,而不是整个n 2筛。 现在,我们的内存中筛子可以覆盖最多约40位数字的数字(以10为基数),而基于磁盘的筛子可以跳至60位数字的数字(以10为基数),减去较大值所需的空间。

没有理由不能进一步采用这种方法-使用小筛子来引导较大的瞬态筛子,然后依次使用它来填充更大的筛子。

但是这需要多长时间?

是的,有摩擦。 初始化n个值的筛网的成本为O(n 2 。 您可以使用各种调整来减少常数,但是到最后,您要访问每个节点一次( O(n) ),然后访问与每个点成比例的与n成比例的滚动值。 值得一提的是,保留CPU的缓存体系结构可能会产生很大的不同。

实际上,任何最新的系统都应能够在几秒钟内创建一个包含前百万个素数的筛子。 将筛子激增到最初的十亿个素数,如果JVM堆空间有限迫使我们大量使用磁盘,那么时间可能会跳到一周,甚至一个月。 我的直觉是,填充TB磁盘需要花费数月甚至数年的服务器场时间

何必呢 ?

对于我们大多数人来说,主要的收获是演示了如何从一个小种子开始收集,比如说一个n = 1000的筛子,并根据需要透明地进行生长。 对于素数,这很容易,但是想像一下RSS提要使用相同的方法并不是一件容易的事。 我们习惯于将Iterators视为Collections的一些乏味方面,但实际上,当用作Iterable的一部分时,它们为我们提供了很多灵活性。

较大的主筛还有一个实际的原因-分解大量。 有多种很好的算法可以分解大量数据,但是它们很昂贵-即使在服务器场中,即使是“少量”数据也可能需要数月或数年。 这就是为什么第一步始终要使用“小”素数进行试验划分的原因-这可能需要一天的时间。

源代码

好消息是我已经为此发布了源代码……坏消息是当我处理Project Euler问题时,这是正在进行的涂鸦的一部分。 (这里没有解决方案-完全是对问题启发的想法的探索。因此,代码有些粗略,不应用于决定是否邀请我参加面试(除非给您留下深刻的印象): http ://github.com/beargiles/projecteuler。

翻译自: https://www.javacodegeeks.com/2014/07/getting-an-infinite-list-of-primes-in-java.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值