Asal sayıları oluşturmanın en zarif yolu [kapalı]


84

Bu işlevi uygulamanın en şık yolu nedir:

ArrayList generatePrimes(int n)

Bu işlev ilk nasal sayıları üretir (düzenleme: nerede n>1), bu nedenlegeneratePrimes(5) bir ArrayListwith döndürür {2, 3, 5, 7, 11}. (Bunu C # ile yapıyorum, ancak bir Java uygulamasından - veya bu konuda benzer başka bir dilden (yani Haskell değil) memnunum).

Bu işlevi nasıl yazacağımı biliyorum ama dün gece yaptığımda umduğum kadar güzel olmadı. İşte bulduğum şey:

ArrayList generatePrimes(int toGenerate)
{
    ArrayList primes = new ArrayList();
    primes.Add(2);
    primes.Add(3);
    while (primes.Count < toGenerate)
    {
        int nextPrime = (int)(primes[primes.Count - 1]) + 2;
        while (true)
        {
            bool isPrime = true;
            foreach (int n in primes)
            {
                if (nextPrime % n == 0)
                {
                    isPrime = false;
                    break;
                }
            }
            if (isPrime)
            {
                break;
            }
            else
            {
                nextPrime += 2;
            }
        }
        primes.Add(nextPrime);
    }
    return primes;
}

Açıkça verimsiz olmasını istemesem de hız konusunda çok endişelenmiyorum. Hangi yöntemin kullanıldığına aldırmıyorum (naif veya elek veya başka herhangi bir şey), ancak oldukça kısa olmasını ve nasıl çalıştığını açık olmasını istiyorum.

Düzenle : Yanıtlayan herkese teşekkürler, ancak çoğu asıl sorumu yanıtlamadı. Tekrarlamak gerekirse, asal sayıların bir listesini oluşturan güzel ve temiz bir kod parçası istedim. Bunu birçok farklı şekilde nasıl yapacağımı zaten biliyorum, ancak olabileceği kadar net olmayan bir kod yazma eğilimindeyim. Bu ileti dizisinde birkaç iyi seçenek önerilmiştir:

  • Başlangıçta sahip olduğumun daha güzel bir versiyonu (Peter Smit, jmservera ve Rekreativc)
  • Eratosthenes (starblue) eleğinin çok temiz bir uygulaması
  • Java'nın BigIntegervenextProbablePrime çok basit bir kod için , ancak özellikle verimli olduğunu hayal edemiyorum (dfa)
  • Asalların listesini (Maghis) tembel olarak oluşturmak için LINQ kullanın
  • Bir metin dosyasına çok sayıda asal koyun ve gerektiğinde bunları okuyun (darin)

Düzenleme 2 : C # ' da burada verilen yöntemlerden birkaçını ve burada bahsedilmeyen başka bir yöntemi uyguladım . Hepsi ilk n primeri etkili bir şekilde bulur (ve eleklere sağlamak için sınırı bulmak için iyi bir yöntemim var ).


12
hayır, ve Project Euler için de değil :-)
David Johnstone

1
çok sayıda <int> değerini yeniden vermem ve tek tek vermem daha iyi olurdu
Felice Pollano

4
Asal sayıları üretmenin en az zarif yolunun ne olduğunu bilmek istiyorum . Access veritabanıyla ilgili bir şey mi düşünüyorum?
j_random_hacker

1
Karşılaştırma için, bir BMeph 2008 Haskell kod : nubBy (((>1).).gcd) [2..]. Daha gcdönce bulunan sayılardan herhangi biriyle 1'den büyük olan herhangi bir sayıyı çift olarak kabul ederken, 2'den başlayarak doğal sayılar arasında yalnızca çift olmayanlar bırakır . Üretilen asal sayıları açısından çok verimsiz, ikinci dereceden. Ama zarif .
Will Ness

En zarif olan IMO, Haskell'e aittir import Data.List.Ordered ; let { _Y g = g (_Y g) ; primes = 2 : _Y( (3:) . minus [5,7..] . unionAll . map (\p-> [p*p, p*p+p*2..]) ) }ama elbette bu tamamen fikir temelli .
Will Ness

Yanıtlar:


48

Tahmini kullanın

pi(n) = n / log(n)

Bir limit bulmak için n'ye kadar asal sayısı için ve sonra bir elek kullanın. Tahmin, n'ye kadar asal sayıların sayısını olduğundan az tahmin ediyor, bu nedenle elek, gerekenden biraz daha büyük olacak, bu da sorun değil.

Bu benim standart Java süzgecim, normal bir dizüstü bilgisayarda yaklaşık bir saniyede ilk milyon asal sayıyı hesaplar:

public static BitSet computePrimes(int limit)
{
    final BitSet primes = new BitSet();
    primes.set(0, false);
    primes.set(1, false);
    primes.set(2, limit, true);
    for (int i = 0; i * i < limit; i++)
    {
        if (primes.get(i))
        {
            for (int j = i * i; j < limit; j += i)
            {
                primes.clear(j);
            }
        }
    }
    return primes;
}

3
Eratosthenes eleğinin çok güzel bir uygulaması
David Johnstone

1
dış döngüdeyken döngü yapmak yeterli olmamalı i <= Math.sqrt(limit)mı?
Christoph

1
@David Johnstone Hayır, pi (n) = n / log (n), ters yönde giden n'ye kadar asal sayısını olduğundan az tahmin ediyor. Yine de çok daha hoş bir yaklaşım bulduğunuza sevindim.
starblue

1
Eğer kendi döngüsünde birçok 2 tümünü kaldırmak isteyen varsa, j + = 2 * i bazı ekstra çalışma zamanını kaydetmek için, ve biraz vardiya kullanarak kez bunu hesaplayabilir sizin döngü artan olarak kullanabileceğiniz
Nick Larsen

5
BitSet2, 3 ve 5 için tekerlek çarpanlarına ayırma uygulayan bir sınıfla değiştirildiğinde , neredeyse 3 kat daha hızlı hale gelir.
starblue

37

Faydalı cevaplar veren herkese çok teşekkürler. İşte ilk n'yi bulmanın birkaç farklı yönteminin uygulamaları C # ' asal sayıları . İlk iki yöntem, hemen hemen burada yayınlananlardır. (Poster isimleri başlığın yanında.) Bir ara Atkin'in elekini yapmayı planlıyorum, ancak şu anda buradaki yöntemler kadar basit olmayacağından şüpheleniyorum. Bu yöntemlerden herhangi birini geliştirmenin bir yolunu gören varsa bilmek isterim :-)

Standart Yöntem ( Peter Smit , jmservera , Rekreativc )

İlk asal sayı 2'dir. Bunu bir asal listesine ekleyin. Bir sonraki asal, bu listedeki herhangi bir sayıya eşit olarak bölünemeyen bir sonraki sayıdır.

public static List<int> GeneratePrimesNaive(int n)
{
    List<int> primes = new List<int>();
    primes.Add(2);
    int nextPrime = 3;
    while (primes.Count < n)
    {
        int sqrt = (int)Math.Sqrt(nextPrime);
        bool isPrime = true;
        for (int i = 0; (int)primes[i] <= sqrt; i++)
        {
            if (nextPrime % primes[i] == 0)
            {
                isPrime = false;
                break;
            }
        }
        if (isPrime)
        {
            primes.Add(nextPrime);
        }
        nextPrime += 2;
    }
    return primes;
}

Bu, yalnızca test edilen sayının kareköküne kadar bölünebilirliği test edilerek optimize edilmiştir; ve yalnızca tek sayıları test ederek. Bu başka biçiminin sadece sayı test ederek optimize edilebilir 6k+[1, 5]ya da 30k+[1, 7, 11, 13, 17, 19, 23, 29]ya da benzeri .

Eratosthenes Eleği ( starblue )

Bu, tüm asal sayıları k'ye bulur . İlk bir listesini yapmak için n asal, yaklaşık değere biz ilk ihtiyaç n inci asal. Burada açıklandığı gibi aşağıdaki yöntem bunu yapar.

public static int ApproximateNthPrime(int nn)
{
    double n = (double)nn;
    double p;
    if (nn >= 7022)
    {
        p = n * Math.Log(n) + n * (Math.Log(Math.Log(n)) - 0.9385);
    }
    else if (nn >= 6)
    {
        p = n * Math.Log(n) + n * Math.Log(Math.Log(n));
    }
    else if (nn > 0)
    {
        p = new int[] { 2, 3, 5, 7, 11 }[nn - 1];
    }
    else
    {
        p = 0;
    }
    return (int)p;
}

// Find all primes up to and including the limit
public static BitArray SieveOfEratosthenes(int limit)
{
    BitArray bits = new BitArray(limit + 1, true);
    bits[0] = false;
    bits[1] = false;
    for (int i = 0; i * i <= limit; i++)
    {
        if (bits[i])
        {
            for (int j = i * i; j <= limit; j += i)
            {
                bits[j] = false;
            }
        }
    }
    return bits;
}

public static List<int> GeneratePrimesSieveOfEratosthenes(int n)
{
    int limit = ApproximateNthPrime(n);
    BitArray bits = SieveOfEratosthenes(limit);
    List<int> primes = new List<int>();
    for (int i = 0, found = 0; i < limit && found < n; i++)
    {
        if (bits[i])
        {
            primes.Add(i);
            found++;
        }
    }
    return primes;
}

Sundaram Elek

Bu eleği sadece yakın zamanda keşfettim , ancak oldukça basit bir şekilde uygulanabilir. Uygulamam Eratosthenes'in eleği kadar hızlı değil, ancak saf yöntemden önemli ölçüde daha hızlı.

public static BitArray SieveOfSundaram(int limit)
{
    limit /= 2;
    BitArray bits = new BitArray(limit + 1, true);
    for (int i = 1; 3 * i + 1 < limit; i++)
    {
        for (int j = 1; i + j + 2 * i * j <= limit; j++)
        {
            bits[i + j + 2 * i * j] = false;
        }
    }
    return bits;
}

public static List<int> GeneratePrimesSieveOfSundaram(int n)
{
    int limit = ApproximateNthPrime(n);
    BitArray bits = SieveOfSundaram(limit);
    List<int> primes = new List<int>();
    primes.Add(2);
    for (int i = 1, found = 1; 2 * i + 1 <= limit && found < n; i++)
    {
        if (bits[i])
        {
            primes.Add(2 * i + 1);
            found++;
        }
    }
    return primes;
}

Bilginize - Bir taşmayı önlemek için ana döngü sayacınızı "for (int i = 0; i * i <= limit && i * i> 0; i ++)" olarak değiştirmem gerekti.
Jacobs Data Solutions

Sundaram Elekinin bu uygulaması, çok az sayıda doğru uygulamadan biridir. Çoğu, i+j+2*i*jyanlış çıktıya yol açan hesaplama yaparken i ve j için yanlış sınırlar kullanır .
jahackbeth

16

Eski bir soruyu yeniden yanıtlarken, LINQ ile oynarken tökezledim.

Bu Kod .NET4.0 veya Paralel Uzantılarla .NET3.5 gerektirir

public List<int> GeneratePrimes(int n) {
    var r = from i in Enumerable.Range(2, n - 1).AsParallel()
            where Enumerable.Range(1, (int)Math.Sqrt(i)).All(j => j == 1 || i % j != 0)
            select i;
    return r.ToList();
}

1
Neden bu kabul edilen cevap değil? Buradaki kod, kabul edilen cevaptaki koddan çok daha kısa, daha zarif ve çok daha hızlıdır. Keşke birden fazla oy verebilseydim!
Avrohom Yisroel

9

İyi yoldasın.

Bazı yorumlar

  • primes.Add (3); bu işlevin sayı = 1 için çalışmamasını sağlar

  • Bölümü, test edilecek sayının kare ayağından daha büyük olan primer sayılarla test etmeniz gerekmez.

Önerilen kod:

ArrayList generatePrimes(int toGenerate)
{
    ArrayList primes = new ArrayList();

    if(toGenerate > 0) primes.Add(2);

    int curTest = 3;
    while (primes.Count < toGenerate)
    {

        int sqrt = (int) Math.sqrt(curTest);

        bool isPrime = true;
        for (int i = 0; i < primes.Count && primes.get(i) <= sqrt; ++i)
        {
            if (curTest % primes.get(i) == 0)
            {
                isPrime = false;
                break;
            }
        }

        if(isPrime) primes.Add(curTest);

        curTest +=2
    }
    return primes;
}

1
karekökü önceden hesaplamak yerine döngüdeki * asal <= curTest'i test etmek, muhtemelen onu daha hızlı hale getirecek ve daha genel hale getirecektir (bignumlar vb. için çalışacak)
yairchu

Neden karekök kullanılıyor? Böyle bir seçeneğin matematiksel arka planı nedir? Muhtemelen ben, sadece 2'ye
bölerim

3
Çünkü bir sayının asal çarpanları varsa, bunlardan en az birinin karekökten küçük veya ona eşit olması gerekir. A * b = c ve a <= b ise a <= sqrt (c) <= b.
David Johnstone

8

olası asal sayılara bir göz atmalısınız . Özellikle Randomize Algoritmalara ve Miller-Rabin asallık testine bir göz atın .

Eksiksizlik uğruna java.math.BigInteger kullanabilirsiniz :

public class PrimeGenerator implements Iterator<BigInteger>, Iterable<BigInteger> {

    private BigInteger p = BigInteger.ONE;

    @Override
    public boolean hasNext() {
        return true;
    }

    @Override
    public BigInteger next() {
        p = p.nextProbablePrime();
        return p;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    public Iterator<BigInteger> iterator() {
        return this;
    }
}

@Test
public void printPrimes() {
    for (BigInteger p : new PrimeGenerator()) {
        System.out.println(p);
    }
}

2
Miller-Rabbin çok hızlı ve kodu çok basit. Yeterli yinelemelerin verilmesi, yanlış pozitif olasılığı açısından rastgele CPU arızasıyla rekabet edecek kadar güvenilir olmasını sağlar. Algoritmanın dezavantajı, aslında neden çalıştığını anlamanın zor bir iş olmasıdır.
Brian

6

Hiçbir şekilde etkili değil, ama belki de en okunaklı olanı:

public static IEnumerable<int> GeneratePrimes()
{
   return Range(2).Where(candidate => Range(2, (int)Math.Sqrt(candidate)))
                                     .All(divisor => candidate % divisor != 0));
}

ile:

public static IEnumerable<int> Range(int from, int to = int.MaxValue)
{
   for (int i = from; i <= to; i++) yield return i;
}

Aslında, buradaki bazı yayınların daha güzel biçimlendirmeye sahip bir varyasyonu.


5

Telif Hakları 2009, St.Wittum 13189 Berlin ALMANYA, CC-BY-SA Lisansı altında https://creativecommons.org/licenses/by-sa/3.0/

TÜM PRİMLERİ hesaplamanın basit ama en zarif yolu bu olacaktır, ancak bu yol yavaştır ve daha yüksek sayılar için bellek maliyetleri çok daha yüksektir, çünkü fakülte (!) İşlevi kullanılır ... ancak bu, Python'da uygulanan algoritma ile tüm asal sayıları oluştur

#!/usr/bin/python
f=1 # 0!
p=2 # 1st prime
while True:
    if f%p%2:
        print p
    p+=1
    f*=(p-2)

4

Primes.txt oluşturmak için bir asal sayı üreteci kullanın ve ardından:

class Program
{
    static void Main(string[] args)
    {
        using (StreamReader reader = new StreamReader("primes.txt"))
        {
            foreach (var prime in GetPrimes(10, reader))
            {
                Console.WriteLine(prime);
            }
        }
    }

    public static IEnumerable<short> GetPrimes(short upTo, StreamReader reader)
    {
        int count = 0;
        string line = string.Empty;
        while ((line = reader.ReadLine()) != null && count++ < upTo)
        {
            yield return short.Parse(line);
        }
    }
}

Bu durumda yöntem imzasında Int16 kullanıyorum, bu yüzden primes.txt dosyam 0'dan 32767'ye kadar sayılar içeriyor. Bunu Int32 veya Int64'e genişletmek isterseniz, primes.txt dosyanız önemli ölçüde daha büyük olabilir.


3
OP'ye atıfta bulunarak: "Hangi yöntemin kullanıldığına (naif veya elek veya başka herhangi bir şey) aldırış etmiyorum, ancak oldukça kısa olmasını ve nasıl çalıştığı açık olmasını istiyorum". Cevabımın tamamen alakalı olduğunu düşünüyorum. Aynı zamanda en hızlı yöntemdir.
Darin Dimitrov

14
"Hangi metodu umursamıyorum ..." dese bile bunun "asalların listesini açmayı" içerdiğini sanmıyorum. Bu, "bilgisayar nasıl yapılır" sorusuna "bilgisayar satın alın" ile cevap vermeye benzer. -1
stevenvh

8
Asal sayıları bir dosyadan okumak yerine kaynak kodun içine yazarsanız daha hızlı olur.
Daniel Daranas

3
Fazla hafıza mı tüketiyorsunuz? tam asal listesini metin olarak okumaktan daha fazlası ... hafızaya? .Net'te dizelerin nasıl çalıştığını biliyor musunuz?
jmservera

6
Asalların listesi sonsuz ama değişmez bir listedir, bu nedenle uygulama için olası üst sınıra kadar önceden hesaplanmış bir liste kullanmak çok mantıklıdır. Gereksinimleri karşılamak için kullanılabilecek doğru bir genel liste varken neden hatalı olabilecek bir kod yazmakla zaman harcayasınız?
AndyM

4

Aşağıdaki C # çözümünü sunabilirim. Hiçbir şekilde hızlı değil, ancak ne yaptığı konusunda çok açık.

public static List<Int32> GetPrimes(Int32 limit)
{
    List<Int32> primes = new List<Int32>() { 2 };

    for (int n = 3; n <= limit; n += 2)
    {
        Int32 sqrt = (Int32)Math.Sqrt(n);

        if (primes.TakeWhile(p => p <= sqrt).All(p => n % p != 0))
        {
            primes.Add(n);
        }
    }

    return primes;
}

Herhangi bir kontrolü bıraktım - limit negatif veya ikiden küçükse (şu an için yöntem her zaman en az ikiyi asal olarak döndürecektir). Ancak bunların düzeltilmesi çok kolay.

GÜNCELLEME

Aşağıdaki iki uzatma yöntemiyle

public static void Do<T>(this IEnumerable<T> collection, Action<T> action)
{
    foreach (T item in collection)
    {
        action(item);
    }
}

public static IEnumerable<Int32> Range(Int32 start, Int32 end, Int32 step)
{
    for (int i = start; i < end; i += step)
    }
        yield return i;
    }
}

aşağıdaki gibi yeniden yazabilirsiniz.

public static List<Int32> GetPrimes(Int32 limit)
{
    List<Int32> primes = new List<Int32>() { 2 };

    Range(3, limit, 2)
        .Where(n => primes
            .TakeWhile(p => p <= Math.Sqrt(n))
            .All(p => n % p != 0))
        .Do(n => primes.Add(n));

    return primes;
}

Daha az etkilidir (çünkü karekök oldukça sık yeniden değerlendirilmiştir) ama daha da temiz bir koddur. Asal sayıları tembel bir şekilde numaralandırmak için kodu yeniden yazmak mümkündür, ancak bu, kodu biraz karıştıracaktır.


Karekök hesaplamasının JIT derleyicisi tarafından optimize edildiğine neredeyse pozitifim (optimizasyon etkinleştirildiğinde derlendiğinde). Bunu, üretilen derlemeyi inceleyerek doğrulamanız gerekir (IL yalnızca kısmen optimize edilmiştir ve JIT derleyicisi tarafından gerçekleştirilen optimizasyona yakın değildir. Döngü kaldırma ve diğer mikro optimizasyon günleri geride kalmıştır. Aslında, bazen zekice JIT kodunuzu yavaşlatabilir .
Dave Black

4

İşte bir uygulama var Eratosthenes Sieve C #:

    IEnumerable<int> GeneratePrimes(int n)
    {
        var values = new Numbers[n];

        values[0] = Numbers.Prime;
        values[1] = Numbers.Prime;

        for (int outer = 2; outer != -1; outer = FirstUnset(values, outer))
        {
            values[outer] = Numbers.Prime;

            for (int inner = outer * 2; inner < values.Length; inner += outer)
                values[inner] = Numbers.Composite;
        }

        for (int i = 2; i < values.Length; i++)
        {
            if (values[i] == Numbers.Prime)
                yield return i;
        }
    }

    int FirstUnset(Numbers[] values, int last)
    {
        for (int i = last; i < values.Length; i++)
            if (values[i] == Numbers.Unset)
                return i;

        return -1;
    }

    enum Numbers
    {
        Unset,
        Prime,
        Composite
    }

bunu enum yerine bool ile yapardım ...
Letterman

3

Aynı algoritmayı kullanarak bunu biraz daha kısaltabilirsiniz:

List<int> primes=new List<int>(new int[]{2,3});
for (int n = 5; primes.Count< numberToGenerate; n+=2)
{
  bool isPrime = true;
  foreach (int prime in primes)
  {
    if (n % prime == 0)
    {
      isPrime = false;
      break;
    }
  }
  if (isPrime)
    primes.Add(n);
}

3

Haskell dışında bir çözüm istediğini biliyorum ama soruyla ilgili olduğu için bunu buraya ekliyorum ve Haskell de bu tür şeyler için çok güzel.

module Prime where

primes :: [Integer]
primes = 2:3:primes'
  where
    -- Every prime number other than 2 and 3 must be of the form 6k + 1 or 
    -- 6k + 5. Note we exclude 1 from the candidates and mark the next one as
    -- prime (6*0+5 == 5) to start the recursion.
    1:p:candidates = [6*k+r | k <- [0..], r <- [1,5]]
    primes'        = p : filter isPrime candidates
    isPrime n      = all (not . divides n) $ takeWhile (\p -> p*p <= n) primes'
    divides n p    = n `mod` p == 0

Evet, ben de Haskell'in büyük bir hayranıyım (keşke daha iyi bilseydim)
David Johnstone

3

Bazı LINQ kullanarak c # dilinde basit bir Eratosthenes uygulaması yazdım.

Maalesef LINQ sonsuz bir ints dizisi sağlamaz, bu nedenle int.MaxValue :(

Önbelleğe alınan her asal için hesaplamaktan kaçınmak için aday sqrt'yi anonim bir türde önbelleğe almak zorunda kaldım (biraz çirkin görünüyor).

Adayın meydanına kadar önceki asalların bir listesini kullanıyorum

cache.TakeWhile(c => c <= candidate.Sqrt)

ve 2'den başlayarak her Int'ye karşı kontrol edin

.Any(cachedPrime => candidate.Current % cachedPrime == 0)

İşte kod:

static IEnumerable<int> Primes(int count)
{
    return Primes().Take(count);
}

static IEnumerable<int> Primes()
{
    List<int> cache = new List<int>();

    var primes = Enumerable.Range(2, int.MaxValue - 2).Select(candidate => new 
    {
        Sqrt = (int)Math.Sqrt(candidate), // caching sqrt for performance
        Current = candidate
    }).Where(candidate => !cache.TakeWhile(c => c <= candidate.Sqrt)
            .Any(cachedPrime => candidate.Current % cachedPrime == 0))
            .Select(p => p.Current);

    foreach (var prime in primes)
    {
        cache.Add(prime);
        yield return prime;
    }
}

Diğer bir optimizasyon, çift sayıları kontrol etmekten kaçınmak ve Listeyi oluşturmadan önce sadece 2 döndürmektir. Bu şekilde, arama yöntemi sadece 1 üssü isterse, tüm karışıklığı önleyecektir:

static IEnumerable<int> Primes()
{
    yield return 2;
    List<int> cache = new List<int>() { 2 };

    var primes = Enumerable.Range(3, int.MaxValue - 3)
        .Where(candidate => candidate % 2 != 0)
        .Select(candidate => new
    {
        Sqrt = (int)Math.Sqrt(candidate), // caching sqrt for performance
        Current = candidate
    }).Where(candidate => !cache.TakeWhile(c => c <= candidate.Sqrt)
            .Any(cachedPrime => candidate.Current % cachedPrime == 0))
            .Select(p => p.Current);

    foreach (var prime in primes)
    {
        cache.Add(prime);
        yield return prime;
    }
}

1
Keşke LINQ'yu bu yanıtı daha iyi anlayacak ve anlayacak kadar tanıyabilseydim :-) Ayrıca, bunun Eratosthenes elekinin bir uygulaması olmadığını ve kavramsal olarak orijinal işlevimle aynı şekilde çalıştığını hissediyorum (sonrakini bul önceden bulunan asal sayılardan herhangi birine bölünemeyen sayı).
David Johnstone

Evet, ancak "daha önce bulunan asal sayılardan herhangi biriyle bölünemeyen bir sonraki sayıyı bul (sayıdan küçük)" kavramsal olarak periodtosthenes elekine benzer. Tercih ederseniz, LINQ'ya aşina olmasanız bile daha okunaklı hale getirmek için biraz yeniden düzenleyebilirim. Yineleyicilere aşina mısınız?
Maghis

Bu yaklaşımdan hoşlandığım şey, bir sonraki asalın, arayan kişi bunu istediğinde hesaplanmasıdır, bu nedenle "ilk n üssü al" veya "n'den daha küçük olan asalları al" gibi şeyler önemsiz hale gelir
Maghis

1
Teşekkürler, ama ne yaptığını az çok bilecek kadar anlayabiliyorum :-) Tembel değerlendirmeyi seviyorum, ama yine de buna Eratosthenes elek uygulaması demem.
David Johnstone

1

Daha zarif hale getirmek için, IsPrime testinizi ayrı bir yöntemde yeniden düzenlemeli ve bunun dışındaki döngü ve artışları işlemelisiniz.


1

Bunu Java'da yazdığım işlevsel bir kitaplığı kullanarak yaptım, ancak kitaplığım Numaralandırmalarla aynı kavramları kullandığından, kodun uyarlanabilir olduğundan eminim:

Iterable<Integer> numbers = new Range(1, 100);
Iterable<Integer> primes = numbers.inject(numbers, new Functions.Injecter<Iterable<Integer>, Integer>()
{
    public Iterable<Integer> call(Iterable<Integer> numbers, final Integer number) throws Exception
    {
        // We don't test for 1 which is implicit
        if ( number <= 1 )
        {
            return numbers;
        }
        // Only keep in numbers those that do not divide by number
        return numbers.reject(new Functions.Predicate1<Integer>()
        {
            public Boolean call(Integer n) throws Exception
            {
                return n > number && n % number == 0;
            }
        });
    }
});

1

Bu, kısa sürede düşünebildiğim en zarif şey.

ArrayList generatePrimes(int numberToGenerate)
{
    ArrayList rez = new ArrayList();

    rez.Add(2);
    rez.Add(3);

    for(int i = 5; rez.Count <= numberToGenerate; i+=2)
    {
        bool prime = true;
        for (int j = 2; j < Math.Sqrt(i); j++)
        {
            if (i % j == 0)
            {
                    prime = false;
                    break;
            }
        }
        if (prime) rez.Add(i);
    }

    return rez;
}

Umarım bu size bir fikir vermeye yardımcı olur. Eminim bu optimize edilebilir, ancak size sürümünüzün nasıl daha zarif hale getirilebileceği konusunda bir fikir vermelidir.

DÜZENLEME: Yorumlarda belirtildiği gibi, bu algoritma gerçekten numberToGenerate <2 için yanlış değerler döndürür. Ona asal sayıları oluşturmak için harika bir yöntem göndermeye çalışmadığımı belirtmek isterim (bunun için Henri'nin cevabına bakın), Metodunun nasıl daha zarif hale getirilebileceğine hemen işaret ediyordum.


3
Bu, numberToGenerate <2 için yanlış bir sonuç döndürüyor
Peter Smit

Bu doğru, ancak bir algoritma tasarlamıyordum, ona sadece yönteminin nasıl daha zarif hale getirilebileceğini gösteriyordum. Yani bu sürüm, ilk sorudaki sürümle aynı derecede yanlış.
David Božjak

1
N = 1 için bozuk olduğu aklıma gelmedi. Soruyu biraz değiştirdim, böylece işlev yalnızca n> 1 için çalışmak zorunda kaldı :-)
David Johnstone

bu asal sayıların karelerini asal sayılar olarak kabul eder.
Will Ness

1

Fonksiyonel Java'da akış tabanlı programlamayı kullanarak aşağıdakileri buldum. Tür Naturalesasen a BigInteger> = 0'dır.

public static Stream<Natural> sieve(final Stream<Natural> xs)
{ return cons(xs.head(), new P1<Stream<Natural>>()
  { public Stream<Natural> _1()
    { return sieve(xs.tail()._1()
                   .filter($(naturalOrd.equal().eq(ZERO))
                           .o(mod.f(xs.head())))); }}); }

public static final Stream<Natural> primes
  = sieve(forever(naturalEnumerator, natural(2).some()));

Artık taşıyabileceğiniz, sonsuz bir asal akışı olan bir değere sahipsiniz. Bunun gibi şeyler yapabilirsiniz:

// Take the first n primes
Stream<Natural> nprimes = primes.take(n);

// Get the millionth prime
Natural mprime = primes.index(1000000);

// Get all primes less than n
Stream<Natural> pltn = primes.takeWhile(naturalOrd.lessThan(n));

Elek hakkında bir açıklama:

  1. Bağımsız değişken akışındaki ilk sayının asal olduğunu varsayın ve onu dönüş akışının önüne koyun. Geri dönüş akışının geri kalanı, yalnızca istendiğinde üretilecek bir hesaplamadır.
  2. Biri akışın geri kalanını sorarsa, argüman akışının geri kalanında elek çağırın, ilk sayıya bölünebilen sayıları filtreleyin (bölümün geri kalanı sıfırdır).

Aşağıdaki içe aktarımlara sahip olmanız gerekir:

import fj.P1;
import static fj.FW.$;
import static fj.data.Enumerator.naturalEnumerator;
import fj.data.Natural;
import static fj.data.Natural.*;
import fj.data.Stream;
import static fj.data.Stream.*;
import static fj.pre.Ord.naturalOrd;

1

Şahsen bunun oldukça kısa ve temiz (Java) bir uygulama olduğunu düşünüyorum:

static ArrayList<Integer> getPrimes(int numPrimes) {
    ArrayList<Integer> primes = new ArrayList<Integer>(numPrimes);
    int n = 2;
    while (primes.size() < numPrimes) {
        while (!isPrime(n)) { n++; }
        primes.add(n);
        n++;
    }
    return primes;
}

static boolean isPrime(int n) {
    if (n < 2) { return false; }
    if (n == 2) { return true; }
    if (n % 2 == 0) { return false; }
    int d = 3;
    while (d * d <= n) {
        if (n % d == 0) { return false; }
        d += 2;
    }
    return true;
}

1

Bu LINQ Sorgusunu deneyin, beklediğiniz gibi asal sayılar üretir

        var NoOfPrimes= 5;
        var GeneratedPrime = Enumerable.Range(1, int.MaxValue)
          .Where(x =>
            {
                 return (x==1)? false:
                        !Enumerable.Range(1, (int)Math.Sqrt(x))
                        .Any(z => (x % z == 0 && x != z && z != 1));
            }).Select(no => no).TakeWhile((val, idx) => idx <= NoOfPrimes-1).ToList();

1
// Create a test range
IEnumerable<int> range = Enumerable.Range(3, 50 - 3);

// Sequential prime number generator
var primes_ = from n in range
     let w = (int)Math.Sqrt(n)
     where Enumerable.Range(2, w).All((i) => n % i > 0)
     select n;

// Note sequence of output:
// 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47,
foreach (var p in primes_)
    Trace.Write(p + ", ");
Trace.WriteLine("");

0

İşte iki milyonun altındaki tüm asal sayıların toplamını yazdıran bir python kodu örneği:

from math import *

limit = 2000000
sievebound = (limit - 1) / 2
# sieve only odd numbers to save memory
# the ith element corresponds to the odd number 2*i+1
sieve = [False for n in xrange(1, sievebound + 1)]
crosslimit = (int(ceil(sqrt(limit))) - 1) / 2
for i in xrange(1, crosslimit):
    if not sieve[i]:
        # if p == 2*i + 1, then
        #   p**2 == 4*(i**2) + 4*i + 1
        #        == 2*i * (i + 1)
        for j in xrange(2*i * (i + 1), sievebound, 2*i + 1):
            sieve[j] = True
sum = 2
for i in xrange(1, sievebound):
    if not sieve[i]:
        sum = sum + (2*i+1)
print sum

0

En basit yöntem deneme yanılmadır: 2 ile n-1 arasındaki herhangi bir sayı aday asal n'yi bölerse denersiniz.
İlk kısayollar elbette a) yalnızca tek sayıları kontrol etmeniz gerekir ve b) yalnızca sqrt (n) 'ye kadar bölücüler için kontrol etmeniz gerekir.

Süreçteki tüm önceki asal sayıları da ürettiğiniz durumda, yalnızca listenizdeki asal sayılardan herhangi birinin, sqrt (n) 'ye kadar, n'yi bölüp bölmediğini kontrol etmeniz gerekir.
Paranız için alabileceğiniz en hızlı şekilde olmalıdır :-)

düzenle
Tamam, kod, siz istediniz. Ama seni uyarıyorum :-), bu 5 dakikalık hızlı ve kirli Delphi kodu:

procedure TForm1.Button1Click(Sender: TObject);
const
  N = 100;
var
  PrimeList: TList;
  I, J, SqrtP: Integer;
  Divides: Boolean;
begin
  PrimeList := TList.Create;
  for I := 2 to N do begin
    SqrtP := Ceil(Sqrt(I));
    J := 0;
    Divides := False;
    while (not Divides) and (J < PrimeList.Count) 
                        and (Integer(PrimeList[J]) <= SqrtP) do begin
      Divides := ( I mod Integer(PrimeList[J]) = 0 );
      inc(J);
    end;
    if not Divides then
      PrimeList.Add(Pointer(I));
  end;
  // display results
  for I := 0 to PrimeList.Count - 1 do
    ListBox1.Items.Add(IntToStr(Integer(PrimeList[I])));
  PrimeList.Free;
end;

1
Ve bunu kodla nasıl ifade ediyorsunuz? :-)
David Johnstone

0

İlk 100 asal sayıyı bulmak için aşağıdaki java kodu düşünülebilir.

int num = 2;
int i, count;
int nPrimeCount = 0;
int primeCount = 0;

    do
    {

        for (i = 2; i <num; i++)
        {

             int n = num % i;

             if (n == 0) {

             nPrimeCount++;
         //  System.out.println(nPrimeCount + " " + "Non-Prime Number is: " + num);

             num++;
             break;

             }
       }

                if (i == num) {

                    primeCount++;

                    System.out.println(primeCount + " " + "Prime number is: " + num);
                    num++;
                }


     }while (primeCount<100);

0

Bunu, Wikki'de "Atkin Sieve" ini ilk okuyarak ve buna verdiğim önceden düşündüğümden elde ettim - sıfırdan kodlama yapmak için çok fazla zaman harcıyorum ve derleyiciye benzer, çok yoğun kodlamamı eleştiren kişilerde tamamen sıfırlanıyorum style + Kodu çalıştırmak için ilk denememi bile yapmadım ... kullanmayı öğrendiğim paradigmaların çoğu burada, sadece okuyun ve ağlayın, elinizden geleni alın.

Herhangi bir kullanımdan önce tüm bunları gerçekten test ettiğinizden kesinlikle ve tamamen emin olun, kesinlikle kimseye göstermeyin - bu fikirleri okumak ve değerlendirmek içindir. İlkellik aracını çalıştırmam gerekiyor, bu yüzden işe yarayan bir şeyi her almam gerektiğinde burada başlarım.

Temiz bir derleme alın, sonra kusurlu olanı elimden almaya başlayın - bu şekilde yapan yaklaşık 108 milyon tuş vuruşum kullanılabilir koda sahibim ...

Yarın kendi versiyonum üzerinde çalışacağım.

package demo;
// This code is a discussion of an opinion in a technical forum.
// It's use as a basis for further work is not prohibited.
import java.util.Arrays;
import java.util.HashSet;
import java.util.ArrayList;
import java.security.GeneralSecurityException;

/**
 * May we start by ignores any numbers divisible by two, three, or five
 * and eliminate from algorithm 3, 5, 7, 11, 13, 17, 19 completely - as
 * these may be done by hand. Then, with some thought we can completely
 * prove to certainty that no number larger than square-root the number
 * can possibly be a candidate prime.
 */

public class PrimeGenerator<T>
{
    //
    Integer HOW_MANY;
    HashSet<Integer>hashSet=new HashSet<Integer>();
    static final java.lang.String LINE_SEPARATOR
       =
       new java.lang.String(java.lang.System.getProperty("line.separator"));//
    //
    PrimeGenerator(Integer howMany) throws GeneralSecurityException
    {
        if(howMany.intValue() < 20)
        {
            throw new GeneralSecurityException("I'm insecure.");
        }
        else
        {
            this.HOW_MANY=howMany;
        }
    }
    // Let us then take from the rich literature readily 
    // available on primes and discount
    // time-wasters to the extent possible, utilizing the modulo operator to obtain some
    // faster operations.
    //
    // Numbers with modulo sixty remainder in these lists are known to be composite.
    //
    final HashSet<Integer> fillArray() throws GeneralSecurityException
    {
        // All numbers with modulo-sixty remainder in this list are not prime.
        int[]list1=new int[]{0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,
        32,34,36,38,40,42,44,46,48,50,52,54,56,58};        //
        for(int nextInt:list1)
        {
            if(hashSet.add(new Integer(nextInt)))
            {
                continue;
            }
            else
            {
                throw new GeneralSecurityException("list1");//
            }
        }
        // All numbers with modulo-sixty remainder in this list are  are
        // divisible by three and not prime.
        int[]list2=new int[]{3,9,15,21,27,33,39,45,51,57};
        //
        for(int nextInt:list2)
        {
            if(hashSet.add(new Integer(nextInt)))
            {
                continue;
            }
            else
            {
                throw new GeneralSecurityException("list2");//
            }
        }
        // All numbers with modulo-sixty remainder in this list are
        // divisible by five and not prime. not prime.
        int[]list3=new int[]{5,25,35,55};
        //
        for(int nextInt:list3)
        {
            if(hashSet.add(new Integer(nextInt)))
            {
                continue;
            }
            else
            {
                throw new GeneralSecurityException("list3");//
            }
        }
        // All numbers with modulo-sixty remainder in
        // this list have a modulo-four remainder of 1.
        // What that means, I have neither clue nor guess - I got all this from
        int[]list4=new int[]{1,13,17,29,37,41,49,53};
        //
        for(int nextInt:list4)
        {
            if(hashSet.add(new Integer(nextInt)))
            {
                continue;
            }
            else
            {
                throw new GeneralSecurityException("list4");//
            }
        }
        Integer lowerBound=new Integer(19);// duh
        Double upperStartingPoint=new Double(Math.ceil(Math.sqrt(Integer.MAX_VALUE)));//
        int upperBound=upperStartingPoint.intValue();//
        HashSet<Integer> resultSet=new HashSet<Integer>();
        // use a loop.
        do
        {
            // One of those one liners, whole program here:
            int aModulo=upperBound % 60;
            if(this.hashSet.contains(new Integer(aModulo)))
            {
                continue;
            }
            else
            {
                resultSet.add(new Integer(aModulo));//
            }
        }
        while(--upperBound > 20);
        // this as an operator here is useful later in your work.
        return resultSet;
    }
    // Test harness ....
    public static void main(java.lang.String[] args)
    {
        return;
    }
}
//eof

0

Bu kodu deneyin.

protected bool isPrimeNubmer(int n)
    {
        if (n % 2 == 0)
            return false;
        else
        {
            int j = 3;
            int k = (n + 1) / 2 ;

            while (j <= k)
            {
                if (n % j == 0)
                    return false;
                j = j + 2;
            }
            return true;
        }
    }
    protected void btn_primeNumbers_Click(object sender, EventArgs e)
    {
        string time = "";
        lbl_message.Text = string.Empty;
        int num;

        StringBuilder builder = new StringBuilder();

        builder.Append("<table><tr>");
        if (int.TryParse(tb_number.Text, out num))
        {
            if (num < 0)
                lbl_message.Text = "Please enter a number greater than or equal to 0.";
            else
            {
                int count = 1;
                int number = 0;
                int cols = 11;

                var watch = Stopwatch.StartNew();

                while (count <= num)
                {
                    if (isPrimeNubmer(number))
                    {
                        if (cols > 0)
                        {
                            builder.Append("<td>" + count + " - " + number + "</td>");
                        }
                        else
                        {
                            builder.Append("</tr><tr><td>" + count + " - " + number + "</td>");
                            cols = 11;
                        }
                        count++;
                        number++;
                        cols--;
                    }
                    else
                        number++;
                }
                builder.Append("</table>");
                watch.Stop();
                var elapsedms = watch.ElapsedMilliseconds;
                double seconds = elapsedms / 1000;
                time = seconds.ToString();
                lbl_message.Text = builder.ToString();
                lbl_time.Text = time;
            }
        }
        else
            lbl_message.Text = "Please enter a numberic number.";

        lbl_time.Text = time;

        tb_number.Text = "";
        tb_number.Focus();
    }

İşte aspx kodu.

<form id="form1" runat="server">
    <div>
        <p>Please enter a number: <asp:TextBox ID="tb_number" runat="server"></asp:TextBox></p>

        <p><asp:Button ID="btn_primeNumbers" runat="server" Text="Show Prime Numbers" OnClick="btn_primeNumbers_Click" />
        </p>
        <p><asp:Label ID="lbl_time" runat="server"></asp:Label></p>
        <p><asp:Label ID="lbl_message" runat="server"></asp:Label></p>
    </div>
</form>

Sonuçlar: Bir saniyeden daha kısa sürede 10000 Asal Sayı

63 saniyede 100000 Asal Sayı

İlk 100 Asal Sayının ekran görüntüsü görüntü açıklamasını buraya girin


1
Bunu deneyerek, sonuçların etkinliğini ve sunumunu ölçebilirim: lütfen zarafetini tartışın.
greybeard

Sonucun şekillendirilmesi sadece ek bir kısımdır. Asal sayı olarak doğru / yanlış döndürme algoritmasını tartışmama izin verin. n% 2, sayıların yarısını ortadan kaldıracaktır çünkü çift sayı her zaman 2'ye bölünebilir. Diğer kodda, yalnızca tek sayılara bölünüyorum, ikiye bölünebilirliği artırıyorum (bu nedenle sonraki bölünebilir de tekdir) asal olan sayının yarısına kadar ya da değil. Neden yarısı, zaman kaybetmemek, çünkü bize kesirli olarak cevap verecek.
riz

log10 (63) ~ = 1.8, yani verileriniz n ^ 1.8 büyüme oranı gösteriyor . Bu çok yavaş; Eratosthenes uygulamalarının optimal eleği: 1.01..1.05; optimal deneme bölümü ~ n ^ 1.35..1.45. Sizin isPrimeNubmergerçekten Suboptimal Tril bölünme uygulamak; Daha da fazla asal üretmeye çalıştığınızda asimptotileri yaklaşık n ^ 2'ye (hatta üstüne) kadar kötüleşecektir.
Will Ness
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.