รหัสที่เร็วที่สุดเพื่อค้นหานายกคนต่อไป


17

ปัญหามีดังนี้

อินพุต:จำนวนเต็มn

เอาท์พุท:nที่เล็กที่สุดที่สำคัญมีขนาดใหญ่กว่า

ความท้าทายคือการให้รหัสที่เร็วที่สุดเท่าที่จะทำได้ ฉันจะทดสอบโค้ดเกี่ยวกับค่าเริ่มต้นที่ขนาด ประมาณ10^8 10^200และเพิ่มเป็นสองเท่าจนกว่าจะใช้เวลามากกว่าหนึ่งนาที 10 วินาทีบนคอมพิวเตอร์ของฉัน

รหัสที่ชนะจะค้นหารหัสเฉพาะถัดไปสำหรับขนาดอินพุตที่ใหญ่ที่สุด

จากการเปรียบเทียบตะแกรงเรียบง่ายที่เขียนด้วยไพ ธ อนสามารถค้นหาไพรม์ถัดไปที่ใหญ่กว่า10^8ใน20เวลาประมาณไม่กี่วินาที

ความต้องการที่ฉันสามารถทดสอบบนคอมพิวเตอร์ 4GB RAM ของฉันเป็น Ubuntu อย่างเข้มงวด รหัสทั้งหมดจะต้องเป็นอิสระ (ทั้งในประสาทสัมผัส) และถ้ามันใช้ไลบรารีพวกเขาจะต้องเป็นอิสระและติดตั้งได้ง่าย ช่วงเวลาที่ผิดพลาดใด ๆ ที่รายงานจะตัดสิทธิ์การส่งทันที

ฉันจะให้รางวัลแก่ผู้ชนะในการเขียนโปรแกรมแต่ละภาษาด้วยเช่นกันหากรหัสนั้นเขียนด้วยภาษานั้นโดยไม่ต้องใช้ห้องสมุดภายนอก ฉันจะเก็บตารางวิ่งในเวลาที่เร็วที่สุดในขณะที่การแข่งขันดำเนินต่อไปเพื่อให้ผู้คนเห็นว่าพวกเขากำลังทำอะไรอยู่

ตารางเท่านี้

  • หลาม น่าอัศจรรย์357หลักที่สำคัญ343239883006530485749095039954069660863471765007165270469723172959277159169882802606127982033072727748864815569574042901856099399985832190628701414555752857600000000000000000000000000000000000000002872284792758930912601189043411951050852357613658978971208596097634095500808832510259693761982135208603287199546795000697807728609476163156438356035166156820611เป็นจำนวนสุดท้ายต่ำกว่า 10 primoวินาทีโดยใช้รหัสที่จัดทำโดย ใครจะชนะรายการแรกนี้หรือไม่

1
เกือบจะซ้ำซ้อนกับCode-Challenge: The Prime ที่ใกล้ที่สุด
Peter Taylor

@ PeterTaylor คำถามนั้นเกี่ยวกับความซับซ้อนของเวลาที่ฉันคิด นี่คือความเร็วจริงในไม่กี่วินาที ฉันคิดว่าสองสิ่งนี้แตกต่างกันมาก
felipa

แน่นอนว่าถ้าคุณติดกับกรณีทดสอบขนาดเล็ก แต่เนื่องจากไม่มีใครสนใจที่จะใช้ AKS สำหรับคำถามอื่นคุณจะได้รับคำตอบเหมือนกัน
Peter Taylor

3
@PeterTaylor อนุญาตให้ฉันไม่เห็นด้วย ในที่สุด 90% ของการเข้าชมของเว็บไซต์ควรจะมาจากเครื่องมือค้นหา การค้นหา google สำหรับการแยกตัวประกอบ semiprime อย่างรวดเร็วและตะแกรงพหุนามกำลังสองหลายแบบคืนค่าปัญหาเดิมที่ฉันได้รับรหัสของฉันจากที่ # 2 และ # 4 ตามลำดับ ฉันคิดว่าในบางประเด็นปัญหานี้จะค่อนข้างสูงfast next prime functionเช่นกัน
primo

1
ฉันคิดว่า OP ล้มเหลวในการอัปเดตการทดสอบคำตอบของเขา ...
mbomb007

คำตอบ:


21

Python ~ 451 หลัก

นี่เป็นส่วนหนึ่งของไลบรารีที่ฉันเขียนสำหรับปัญหาการแยกตัวประกอบ semiprimeโดยลบฟังก์ชันที่ไม่จำเป็น มันใช้การทดสอบแบบดั้งเดิมของ Baillie-PSWซึ่งเป็นการทดสอบความน่าจะเป็นทางเทคนิค แต่จนถึงปัจจุบันยังไม่มี pseudoprimes ที่รู้จักกัน - และยังมีรางวัลเงินสดหากคุณสามารถหาหลักฐานได้ (หรือหาหลักฐานที่ไม่มี) .

แก้ไข : ฉันไม่ได้ตระหนักว่า Python มีการยกกำลังแบบแยกส่วนในตัว การแทนที่ของฉันเองเพื่อผลลัพธ์ในตัวจะช่วยเพิ่มประสิทธิภาพได้ประมาณ 33%

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

สคริปต์ทดสอบตัวอย่าง:

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 เนื่องจากเป็นรากที่สองโดยประมาณโดย10000เพิ่ม 2.5 หลักต่อการวนซ้ำ (และเนื่องจากการเสแสร้งช้าเกินกว่าจะนั่งได้) เอาท์พุทแสดงจำนวนหลักปัจจุบันและเวลาที่ใช้

ตัวอย่างผลลัพธ์:

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

ตอนนี้ทุกรหัสรองรับ python 3 แล้ว


เร็วอย่างน่าอัศจรรย์! ฉันจะเรียกใช้อย่างถูกต้องด้วยขนาดที่เพิ่มเป็นสองเท่าในไม่กี่วัน (และการทดสอบแบบดั้งเดิมที่กำหนดค่าได้) และใส่หมายเลขที่ใหญ่ที่สุดในตาราง ฉันสงสัยว่าคุณอาจเป็นผู้ชนะอยู่แล้ว
felipa

1
FWIW ใน Sage ใช้เวลาnext_prime((2^520)*(10^200))ประมาณ 15 วินาทีบนเครื่องของฉันดังนั้นในตอนแรกที่หน้าแดงมันน่าประทับใจมาก อย่างไรก็ตาม ... next_prime((2^520)*(10^200),proof=False)ใช้เวลา 0.4 วินาทีเพราะมันจะตรวจสอบ pseudoprimality เท่านั้น การเรียกร้องของคุณ "ไม่มี pseudoprimes ที่รู้จักกันดี" กำลังโน้มน้าวใจอย่างไม่น่าเชื่อเนื่องจากจำนวนบิตมีมากกว่า 64 บิตสำหรับ 357 หลักฉันไม่ได้แม้แต่จะเชื่อในระยะไกลจากการไม่มีคู่กรณี
บูธตาม

@boothby มันคุ้มค่าที่จะสังเกตว่านี่เป็นวิธีการเดียวกับที่เมเปิ้ลใช้ วิธีการนั้นถูกตีพิมพ์เมื่อ 33 ปีที่แล้วและยังไม่มีผู้ใดปลอมนามว่าพูดถึงระดับความถูกต้องแม่นยำ
โม่

1
นี่คือเหตุผลที่ฉันใช้ Sage "ไม่ทราบว่าล้มเหลว" จริง ๆ แล้วไม่เหมือนกับ "รู้จักทำงาน" สมมติว่ามี pseudoprime เท็จหนึ่งอันที่ต่ำกว่า 400 หลัก มันต้องใช้เวลาหลายล้านล้านปีในการค้นหามัน - แต่มันก็ยังอยู่ที่นั่น, ทำลายความพยายามใด ๆ เพื่อพิสูจน์ 'pseudoprime = prime' ฉันมักจะ downvote "วิธีแก้ปัญหา" ที่ใช้วิธีการแบบ probabalistic โดยไม่มีการรับประกัน มอนติคาร์โล? แน่นอนค่ะ "มันยอดเยี่ยม 'เพราะพ่อมดบอกว่ามันน่าจะใช่" Nope
บูธตาม

1
@boothby คุณต้องเพิ่มคำตอบเพื่อให้เราสามารถแสดงความคิดเห็นภายใต้มัน :)
felipa

6

C ++ พร้อม GMP: 567 หลัก

ใช้การนำ Miller-Rabin มาใช้ใน GMP มันอาจส่งคืนค่าบวกที่เป็นเท็จ แต่โชคดีที่กระทบกับความน่าจะเป็นที่ 2 ^ -200

#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 + 361(567 หลัก) ก่อนที่จะใช้เวลานานบนแล็ปท็อปที่ช้าของฉัน


3

Perl พร้อมโมดูล GMP ตัวเลข 1300 หลัก

ใช้โมดูลMath :: Prime :: UtilและGMP back endท้ายจุดไขว้ที่แน่นอนจะขึ้นอยู่กับคอมพิวเตอร์ของคุณและคุณมีห้องสมุด GMP ล่าสุดหรือไม่ รหัสทั้งหมดเป็นอิสระ (โมดูลอยู่บน github และ CPAN และ GMP ใช้ได้อย่างอิสระ) ฉันใช้งานพวกเขาบน Ubuntu ของ AWS รวมถึงเดสก์ท็อป Ubuntu (และ Fedora และ AIX และ NetBSD เป็นต้น)

รหัสหลักอยู่ใน C และ C + GMP next_prime จาก MPU เห็นว่าตัวเลขมีขนาดใหญ่เกินไปและส่งต่อไปยังส่วนหลังของ GMP (หรือรหัส Perl บริสุทธิ์หากไม่ได้ติดตั้ง back end) ที่ stringiify และแปลงเป็น mpz และจะแปลงผลลัพธ์กลับไปเป็นประเภทวัตถุอินพุตหรือ Math :: BigInt next_prime ทำเอง:

  • ล้อ mod 30
  • ติดตาม mod ที่เหลือ 23 # เพื่อให้สามารถทำ modulos แบบดั้งเดิมได้สูงสุด 23 ครั้ง
  • การทดสอบที่สำคัญน่าจะเป็นในสิ่งที่ผ่านเหล่านี้

การทดสอบที่สำคัญน่าจะเป็น:

  • ตรวจสอบตัวหารขนาดเล็กโดยใช้ mpz_gcd_ui (ใน 64- บิตที่สองของการตรวจสอบเหล่านี้มากถึง 101)
  • ตรวจสอบตัวหารขนาดเล็กโดยใช้การคำนวณขนาดใหญ่แบบคำนวณเดี่ยว นี่เป็นช่วงเวลาที่มากถึง 10k หรือ 40k ขึ้นอยู่กับขนาดอินพุต
  • สำหรับค่าที่มากกว่า 2 ^ 1600 ให้ทำการหารทดลองเพิ่มเติมโดยใช้ treesieve สิ่งนี้สามารถทำได้อย่างมีประสิทธิภาพมากขึ้น
  • ในที่สุด ES BPSW ก็เสร็จสิ้น (การทดสอบ Miller-Rabin ที่มีฐาน 2 ตามด้วยการทดสอบลูคัสที่แข็งแกร่งเป็นพิเศษ )

ทุกอย่างก่อน ES BPSW เป็นเพียงการเพิ่มประสิทธิภาพซึ่งแน่นอนว่าเราต้องการสำหรับ next_prime next_prime ยังใช้งานใน Perl โดยใช้โมดูล Math :: BigInt (ในแกนกลางที่มีตัวเลือก Pari และ GMP หลังสิ้นสุด) นั่นคือ AES BPSW (เช่น Pari) แต่ไม่ได้รับการปรับให้เหมาะสม

ฉันคิดเกี่ยวกับข้อดีของรุ่นที่ใช้ตะแกรงบางส่วนโดยใช้ช่วงของ 2 ตัวอย่าง ฉันไม่แน่ใจว่าสิ่งนี้จะดีกว่าจริง ๆ หรือเปล่าเพราะส่วนใหญ่เราจะทำการกรองที่ไม่จำเป็นเนื่องจากช่องว่างมีขนาดเล็กและบางครั้งสำหรับช่องว่างขนาดใหญ่เราต้องทำซ้ำหลายครั้ง

ห้องสมุดใช้ ECPP (รวมถึงใบรับรอง) ดังนั้นเราจึงสามารถเรียกใช้การพิสูจน์ผลได้ แต่ 1200 หลักนั้นใหญ่เกินไปสำหรับชุดเริ่มต้นเล็ก ๆ ที่ประกอบด้วยหลายชื่อรวม (มีวิธีสำหรับการดาวน์โหลดชุดใหญ่ - การพิสูจน์จะใช้เวลาน้อยกว่า 15 นาทีซึ่งเร็วกว่า Pari APR-CL เล็กน้อย แต่ช้ากว่าของ WraithX's mpz_aprcl) ข้อเสียหนึ่งของ ECPP เทียบกับ APR-CL คือมันมีความแปรปรวนของเวลามากกว่าดังนั้นจึงมีโอกาสที่ดีที่มันจะเกิน 10 วินาทีในจำนวนหนึ่งก่อนที่เวลาเฉลี่ยจะมาถึง ด้วยหลักฐานฉันคิดว่าเรา จำกัด บางสิ่งบางอย่างในช่วง 400 หลักยกเว้นว่าเราอนุญาตให้ใช้ซอฟต์แวร์แบบมัลติเธรด

#!/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 มันมีตัวเลข 1191 หลักซึ่งเป็นที่ที่เราชนกับช่องว่างของ 18138 ฉันยังทดสอบโค้ดของ primo โดยใช้ my_math.py ล่าสุด มันได้ตัวเลข 630 หลักด้วยลำดับ 10 ^ e และ 641 ตามลำดับของเขา น่าประทับใจมากสำหรับรหัส all-Python ขนาดกะทัดรัดโดยไม่มีการทดสอบจำนวนมาก


ฉันยังไม่สามารถเข้าใจได้ว่าโมดูลนี้เร็วแค่ไหน มันตอบสนองความสนใจของฉันใน perl เป็นเครื่องมือตัวเลข ขณะนี้ฉันกำลังเขียนใหม่Math::GMPในลักษณะที่ไม่ค่อยสิ้นเปลืองกับการสร้าง / ทำลายการอ้างอิง mpz
โม่

งานจริงทั้งหมดอยู่ใน C + GMP ดังนั้นมันจึงสามารถทำงานใน Python ได้เช่นกัน Python มีข้อได้เปรียบที่ร้ายแรงมากกว่า Perl 5 สำหรับคนจำนวนมากที่ฉันต้องการสามารถแก้ไขได้ Math :: GMPz นั้นเร็วกว่า Math :: GMP และโดยพื้นฐานแล้วก็มีทั้ง mpz API ที่เปิดเผยแม้ว่าจะมีความเปราะบางและแปลกไปบ้างในบางครั้ง การแก้ไขบางสิ่งใน Math :: GMP อยู่ในรายการสิ่งที่ต้องทำของฉันเบื้องหลังสิ่งอื่น ๆ มากมายเกินไป Re MPU ฉันคิดเกี่ยวกับการคว่ำการพัฒนาและทำให้มันกลายเป็นสองไลบรารี C จากนั้นมีโมดูล Perl เพียงแค่ใช้มัน มันจะช่วยให้มันใช้ที่อื่น
DanaJ

ฉันกำลังก้าวหน้าที่ดี ห่วงวิ่งต่อไปกว่า 10 ครั้งเร็ว$x = new Math::GMP(0); $x += 3 for 1..1000000แต่เพียงผู้เดียวเนื่องจากการจัดการการอ้างอิงที่ดีกว่า: ฉันจะโพสต์ไปที่ cpan เมื่อเสร็จแล้ว คุณจะเป็นคนแรกที่รู้;)
โม่
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.