Bir sonraki üssü bulmak için en hızlı kod


17

Sorun şu şekildedir.

Girdi: Bir tam sayın

Çıktı: En küçük asal daha büyük n.

Zor olan, bunu yapmak için mümkün olan en hızlı kodu vermektir. Ben kabaca10^8 boyutundan başlayan 10^200ve daha fazla sürer kadar boyutta iki katına çıkan değerlerin kodunu test Bilgisayarımda bir dakikadan 10 saniye edeceğim.

Kazanan kod , en büyük girdi boyutu için bir sonraki asalı bulur.

Karşılaştırma yapmak gerekirse, python ile yazılmış basit bir elek bir sonraki asalı 10^8yaklaşık olarak daha büyük bulabilir.20 saniyeler .

4GB RAM ubuntu bilgisayarımda test edebilme gereksinimi katıdır. Tüm kodlar ücretsiz olmalı (her iki anlamda da) ve kütüphaneler kullanıyorsa ücretsiz ve kolayca kurulabilir olmalıdır. Bildirilen herhangi bir yanlış prim, başvuruyu derhal diskalifiye edecektir.

Kod tamamen bu kütüphanede harici kütüphaneler kullanılmadan yazılmışsa, her programlama dilinde kazananlar için ayrı övgüler vereceğim. Yarışma devam ettikçe, insanların nasıl yaptıklarını görebilmeleri için en hızlı zamanların koşu masasını da tutacağım.

Masa şimdiye kadar

  • Python. Şaşırtıcı bir 357rakam asal 343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611, verilen kodu kullanarak 10 saniyenin altındaki son sayıdır primo. Bu ilk girişi kimse yenecek mi?


@PeterTaylor Bu soru bence zaman karmaşıklığıyla ilgili. Bu, saniyeler içinde pratik hız ile ilgilidir. Bence bu iki şey oldukça farklı olabilir.
felipa

Tabii, eğer küçük test durumlarına bağlı kalırsanız. Ancak kimse diğer soru için AKS'yi uygulama zahmetine girmediği için aynı cevapları alacaksınız.
Peter Taylor

3
@ PeterTaylor katılmama izin ver. Sonunda, bir sitenin trafiğinin% 90'ı arama motorlarından gelmelidir . Hızlı semiprime çarpanlara ayırma ve Çoklu Polinom Karesel Elek için bir google araması, kodumu sırasıyla # 2 ve # 4 numaralı konumdan aldığım orijinal sorunu döndürüyor. Bir noktada, bu sorunun da oldukça yüksek olacağını hayal ediyorum fast next prime function.
primo

1
Bence OP cevapları testlerini güncelleyemedi ...
mbomb007

Yanıtlar:


21

Python ~ 451 basamak

Bu, gereksiz işlevler kaldırılmış olarak, yarı-yarı çarpanlara ayırma sorunu için yazdığım kütüphanenin bir parçasıdır . Teknik olarak olasılıklı bir test olan Baillie-PSW öncelik testini kullanır , ancak bugüne kadar bilinen bir sahte ilaç yoktur - ve birini bulabiliyorsanız (veya hiç var olmadığına dair bir kanıt sağladığınızda) nakit para ödülü bile vardır. .

Edit : Python yerleşik modüler üs var olduğunu fark etmemişti. Yerleşik için kendiminkini değiştirmek yaklaşık% 33'lük bir performans artışı sağlıyor.

my_math.py

# legendre symbol (a|m)
# note: returns m-1 if a is a non-residue, instead of -1
def legendre(a, m):
  return pow(a, (m-1) >> 1, m)

# strong probable prime
def is_sprp(n, b=2):
  d = n-1
  s = 0
  while d&1 == 0:
    s += 1
    d >>= 1

  x = pow(b, d, n)
  if x == 1 or x == n-1:
    return True

  for r in range(1, s):
    x = (x * x)%n
    if x == 1:
      return False
    elif x == n-1:
      return True

  return False

# lucas probable prime
# assumes D = 1 (mod 4), (D|n) = -1
def is_lucas_prp(n, D):
  P = 1
  Q = (1-D) >> 2

  # n+1 = 2**r*s where s is odd
  s = n+1
  r = 0
  while s&1 == 0:
    r += 1
    s >>= 1

  # calculate the bit reversal of (odd) s
  # e.g. 19 (10011) <=> 25 (11001)
  t = 0
  while s > 0:
    if s&1:
      t += 1
      s -= 1
    else:
      t <<= 1
      s >>= 1

  # use the same bit reversal process to calculate the sth Lucas number
  # keep track of q = Q**n as we go
  U = 0
  V = 2
  q = 1
  # mod_inv(2, n)
  inv_2 = (n+1) >> 1
  while t > 0:
    if t&1 == 1:
      # U, V of n+1
      U, V = ((U + V) * inv_2)%n, ((D*U + V) * inv_2)%n
      q = (q * Q)%n
      t -= 1
    else:
      # U, V of n*2
      U, V = (U * V)%n, (V * V - 2 * q)%n
      q = (q * q)%n
      t >>= 1

  # double s until we have the 2**r*sth Lucas number
  while r > 0:
      U, V = (U * V)%n, (V * V - 2 * q)%n
      q = (q * q)%n
      r -= 1

  # primality check
  # if n is prime, n divides the n+1st Lucas number, given the assumptions
  return U == 0

# primes less than 212
small_primes = set([
    2,  3,  5,  7, 11, 13, 17, 19, 23, 29,
   31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
   73, 79, 83, 89, 97,101,103,107,109,113,
  127,131,137,139,149,151,157,163,167,173,
  179,181,191,193,197,199,211])

# pre-calced sieve of eratosthenes for n = 2, 3, 5, 7
indices = [
    1, 11, 13, 17, 19, 23, 29, 31, 37, 41,
   43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
   89, 97,101,103,107,109,113,121,127,131,
  137,139,143,149,151,157,163,167,169,173,
  179,181,187,191,193,197,199,209]

# distances between sieve values
offsets = [
  10, 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6,
   6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4,
   2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6,
   4, 2, 4, 6, 2, 6, 4, 2, 4, 2,10, 2]

max_int = 2147483647

# an 'almost certain' primality check
def is_prime(n):
  if n < 212:
    return n in small_primes

  for p in small_primes:
    if n%p == 0:
      return False

  # if n is a 32-bit integer, perform full trial division
  if n <= max_int:
    i = 211
    while i*i < n:
      for o in offsets:
        i += o
        if n%i == 0:
          return False
    return True

  # Baillie-PSW
  # this is technically a probabalistic test, but there are no known pseudoprimes
  if not is_sprp(n): return False
  a = 5
  s = 2
  while legendre(a, n) != n-1:
    s = -s
    a = s-a
  return is_lucas_prp(n, a)

# next prime strictly larger than n
def next_prime(n):
  if n < 2:
    return 2
  # first odd larger than n
  n = (n + 1) | 1
  if n < 212:
    while True:
      if n in small_primes:
        return n
      n += 2

  # find our position in the sieve rotation via binary search
  x = int(n%210)
  s = 0
  e = 47
  m = 24
  while m != e:
    if indices[m] < x:
      s = m
      m = (s + e + 1) >> 1
    else:
      e = m
      m = (s + e) >> 1

  i = int(n + (indices[m] - x))
  # adjust offsets
  offs = offsets[m:]+offsets[:m]
  while True:
    for o in offs:
      if is_prime(i):
        return i
      i += o

Örnek bir test komut dosyası:

from time import clock
from my_math import *

n = i = 317**79
while True:
  i *= 317
  time1 = clock()
  n, o = next_prime(i), n
  span = clock()-time1
  if span > 10:
    break
  print(len(str(n)), span)
print(o)

317 faktörü seçildi, çünkü 10000yineleme başına kabaca 2,5 basamak ekleyerek (ve ikiye katlanmak için çok yavaş olduğu için) yaklaşık kare kökü . Çıktı, mevcut basamak sayısını ve alınan süreyi gösterir.

Örnek sonuçlar:

201 0.13121248650317288
203 0.059535499623555505
206 0.9157767258129175
208 0.2583420518529589
211 0.15367400046653978
213 0.32343915218274955
216 1.3962866788935466
218 0.5986165839513125
221 0.973842206202185
223 2.346910291671148
...
428 0.932809896229827
431 4.345940056627313
433 9.511724255457068
436 6.089835998709333
438 1.3793498894412721
441 4.290633027381972
443 3.5102506044762833
446 3.1629148397352083
448 3.364759208223404
451 7.34668009481652
1551197868099891386459896063244381932060770425565921999885096817830297496627504652115239001983985153119775350914638552307445919773021758654815641382344720913548160379485681746575245251059529720935264144339378936233043585239478807971817857394193701584822359805681429741446927344534491412763713568490429195862973508863067230162660278070962484418979417980291904500349345162151774412157280412235743457342694749679453616265540134456421369622519723266737913

Tüm kodlar artık python 3 uyumludur.


Şaşırtıcı derecede hızlı! Birkaç gün içinde iki katına (ve deterministik bir öncelik testine) uygun şekilde çalıştıracağım ve en büyük sayıyı tabloya koyacağım. Yine de kazanan olabileceğinizden şüpheleniyorum.
felipa

1
FWIW, Sage'de, next_prime((2^520)*(10^200))makinemde yaklaşık 15 saniye, bu yüzden ilk allıkta oldukça etkileyici. Ancak ... next_prime((2^520)*(10^200),proof=False)sadece taklitçiliği kontrol ettiği için 0.4 saniye sürer. "Bilinen hiçbir sahte ilaç yok" iddianız, bit sayısı 64'ün üzerine çıktığında ortadan kayboluyor. 357 basamak için, karşı örneklerin eksikliğinden bile ikna olmadım.
boothby

@boothby, bunun Maple tarafından kullanılan aynı yöntem olduğunu belirtmek gerekir . Yöntemin 33 yıl önce yayınlanmış olması ve hala bilinen hiçbir yalancı isim doğruluk derecesi için konuşmuyor.
primo

1
Bu yüzden Sage kullanıyorum. "Başarısız olduğu bilinmemektedir" aslında "çalıştığı bilinen" ile aynı değildir. Varsayalım ki, 400 basamağın altında bir sahte yalancı suç vardır. Onu bulmak trilyonlarca yıl alacaktı - ama yine de orada olacak ve 'yalancı suç = asal' ispatlamak için herhangi bir girişimde bulunmayacaktı. Her zaman sıfır garantili olasılıksal yöntemler kullanan "çözümleri" küçümseyeceğim. Monte Carlo? Tabi ki. "Bir asıl sihirbaz bana bunun büyük olasılıkla olduğunu söyledi çünkü"? Hayır!
boothby

1
@boothby Bir cevap eklemeniz gerekiyor, böylece altına yorum yapabiliriz :)
felipa

6

GMP'li C ++: 567 basamak

GMP'de Miller-Rabin uygulamasını kullanır. Yanlış bir pozitif geri dönebilir, ancak iyi şanslar 2 ^ -200 olasılığı olan birine çarpıyor.

#include <gmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>

double time() {
  struct timeval t;
  gettimeofday(&t, NULL);
  return t.tv_usec  * 1e-6 + t.tv_sec;
}

int main(int argc, char *argv[]) {
  mpz_t n, m;
  mpz_init_set_ui(n, 10);
  mpz_pow_ui(n, n, 200);
  mpz_init(m);
  for (int i = 0; true; i++, mpz_mul_ui(n, n, 2)) {
    double start = time();
    for (mpz_add_ui(m, n, 1); !mpz_millerrabin(m, 100); mpz_add_ui(m, m, 2)) ;
    double t = time() - start;
    gmp_printf("%d %Zd %f\n", i, m, t);
    if (t > 10.0) break;
  }
}

10^200 * 2^1216 + 361Yavaş dizüstü bilgisayarımda zaman içinde çalışmadan önce asal (567 basamaklı) bulur .


3

GMP modülü ile Perl, 1300 basamak

Modemimi kullanarak Math :: Prime :: Util ve GMP arka ucu . Kesin geçiş noktası bilgisayarınıza ve en son GMP kütüphanesine sahip olup olmadığınıza bağlı olacaktır. Tüm kodlar ücretsizdir (modüller github ve CPAN üzerindedir ve GMP serbestçe kullanılabilir). AWS'nin Ubuntu'nun yanı sıra bir masaüstü Ubuntu (ve Fedora ve AIX ve NetBSD, vb.) Üzerinde çalıştırdım.

Çekirdek kod C ve C + GMP'dir. MPU'dan next_prime, sayının çok büyük olduğunu görür ve GMP arka ucuna iletir (veya arka uç yüklü değilse saf Perl kodunu). Bu, bir mpz'yi diziler ve dönüştürür ve sonucu tekrar giriş nesnesi türüne veya Math :: BigInt'e dönüştürür. next_prime'ın kendisi:

  • bir mod 30 tekerlek
  • Kalan mod 23 # 'ı izler, böylece 23'e kadar olan primerler için doğal modlos yapabilir
  • bunları geçen şeyler üzerinde olası asal test.

Muhtemel asal test:

  • mpz_gcd_ui kullanarak küçük bölücüler olup olmadığını kontrol edin (101 bite kadar bu kontrollerin 64 bit ikisinde)
  • tek hesaplanmış büyük primoriallar kullanarak küçük bölenleri kontrol edin. Bu, giriş boyutuna bağlı olarak 10k veya 40k'ye kadar primerlerdir.
  • 2 ^ 1600'den büyük değerler için, bir treesieve kullanarak daha fazla deneme bölümü gerçekleştirir. Bu daha verimli bir şekilde yapılabilir.
  • Son olarak, ES BPSW yapılır (baz 2 ile Miller-Rabin testi ve ardından ekstra güçlü bir Lucas testi ).

ES BPSW öncesi her şey sadece optimizasyon, tabii ki next_prime için istiyoruz. next_prime Perl'de Math :: BigInt modülü (isteğe bağlı Pari ve GMP arka uçları ile birlikte) kullanılarak da uygulanır. Bu AES BPSW'yi (Pari gibi) yapar, ancak optimize edilmez.

Kısmi elek tabanlı bir versiyonun esasını, örneğin 2 değer kullanarak düşündüm. Bunun gerçekten daha iyi olup olmayacağından emin değilim, çoğu zaman boşluk küçük olduğu için gereksiz eleme yapıyoruz ve bazen büyük bir boşluk için bunu birkaç kez tekrarlamamız gerekiyordu.

Kütüphane ECPP'yi (sertifikalar dahil) uygular, böylece sonuç üzerinde bir kanıt çalıştırabiliriz, ancak 1200 basamak, dahil edilen polinomların küçük varsayılan seti için gerçekten çok büyük (daha büyük kümeleri indirmek için bir yöntem var - kanıtlar biraz altına girer) 15 dakika, Pari'nin APR-CL'sinden biraz daha hızlı, ancak WraithX'in mpz_aprcl'sinden biraz daha yavaş). ECPP'ye karşı APR-CL'nin bir dezavantajı, daha fazla zaman varyansına sahip olmasıdır, bu nedenle ortalama süre oraya ulaşmadan önce bazı sayılarda 10 saniyeyi aşma şansı yüksektir. Bir kanıtla, çok iş parçacıklı yazılıma izin vermediğimiz sürece 400 basamaklı bir aralıkta sınırlı olduğumuzu düşünüyorum.

#!/usr/bin/env perl
use warnings;
use strict;
use Math::Prime::Util ":all";
use Math::Prime::Util::GMP;  # Barf if the backend isn't installed
use Time::HiRes qw(gettimeofday tv_interval);
use Math::GMP;

my $n = Math::GMP->new(10) ** 200;
while (1) {
  my $start = [gettimeofday];
  my $np = next_prime($n);
  my $sec = tv_interval($start);
  my $len = length($n);
  die "next_prime $len = +",$np-$n," in $sec seconds\n" if $sec > 10;
  warn "  next_prime $len = +",$np-$n," in $sec seconds\n";
  $n *= 10;
}

Primo tarafından kullanılanla aynı diziyi denemeye karar verdim. Bu, 18138'lik bir boşluğa çarptığımız için 1191 basamağa ulaştı. En son my_math.py kullanarak primo'nun kodunu da test ettim. 10 ^ e dizisiyle 630 basamağa ve dizisiyle 641 basamağa ulaşır. Çok sayıda ön test yapılmadan kompakt all-Python kodu için çok etkileyici.


Bu modülün ne kadar hızlı olduğunu hala anlayamıyorum. Bir sayı gevrekleştirme aracı olarak perl'e olan ilgimden söz etti. Şu anda Math::GMPmpz referans oluşturma / imha ile çok boşa olmayan bir şekilde yeniden yazıyorum.
primo

Gerçek iş C + GMP'de, bu yüzden Python'da da çalışabilir. Python, Perl 5'e göre büyük sayılar için bazı ciddi avantajlara sahiptir, keşke çözülebilir. Math :: GMPz, bu arada, Math :: GMP'den daha hızlıdır ve temelde tüm mpz API'sine sahiptir, ancak bazen daha kırılgan ve biraz gariptir. Math :: GMP'de bazı şeyleri düzeltmek, çok daha fazla şeyin arkasındaki yapılacaklar listemde. MPU olarak, gelişimi tersine çevirmeyi ve iki C kütüphanesine dönüştürmeyi düşündüm, sonra Perl modülünün bunu kullanmasını sağladım. Başka bir yerde kullanılmasına yardımcı olur.
DanaJ

İyi ilerleme kaydediyorum. Aşağıdaki döngü çalışır hızlı şekilde üzerinde 10 kat yalnızca nedeniyle daha iyi referans yönetimine,: $x = new Math::GMP(0); $x += 3 for 1..1000000. İşim bittiğinde cpan'a göndereceğim; bilen ilk kişilerden biri olacaksın;)
primo
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.