Супер быстрая функция totient

22

Цель проста: вычислить функцию totenent для максимально возможного числа чисел за 10 секунд и суммировать числа.

Вы должны напечатать свой результат в конце, и вы должны фактически рассчитать его. Автоматические функции не допускаются, но библиотеки bignum разрешены. Вы должны начать с 1 и последовательно подсчитать все целые числа. Вам не разрешено пропускать номера.

Ваша оценка состоит в том, сколько чисел ваша программа может рассчитать на вашем компьютере / сколько моя программа может рассчитать на вашем компьютере . Мой код - простая программа на C ++ (без оптимизации), надеюсь, вы сможете ее запустить.

Важные свойства, которые вы могли бы использовать!

  • если gcd(m,n) = 1, phi(mn) = phi(m) * phi(n)
  • если pпростое число, phi(p) = p - 1(для p < 10^20)
  • если nдаже,phi(2n) = 2 phi(n)
  • другие перечислены в первой ссылке

Мой код

#include <iostream>
using namespace std;

int gcd(int a, int b)
{
    while (b != 0)
    {
        int c = a % b;
        a = b;
        b = c;
    }
    return a;
}

int phi(int n)
{
    int x = 0;
    for (int i=1; i<=n; i++)
    {
        if (gcd(n, i) == 1)
            x++;
    }
    return x;
}

int main()
{
    unsigned int sum = 0;
    for (int i=1; i<19000; i++) // Change this so it runs in 10 seconds
    {
        sum += phi(i);
    }
        cout << sum << endl;
        return 0;
}
qwr
источник
2
Возможно, вы захотите добавить, что входные числа должны быть последовательными целыми числами. В противном случае у меня может возникнуть соблазн вычислить функцию тока только для степеней 2.
Говард
Могу ли я сделать 1, 3, 5, 2, 4или как?
Утренняя монахиня

Ответы:

14

Нимрод: ~ 38 667 (580 000 000/15 000)

Этот ответ использует довольно простой подход. В коде используется простое сито простых чисел, которое хранит простое число наименьшей простой степени в каждом слоте для составных чисел (ноль для простых чисел), затем использует динамическое программирование для построения функции-накопителя в том же диапазоне, а затем суммирует результаты. Программа тратит практически все свое время на создание сита, а затем вычисляет суммарную функцию за долю времени. Похоже, все сводится к созданию эффективного сита (с небольшим уклоном в то, что нужно также уметь извлекать из результата главный фактор для составных чисел и поддерживать разумное использование памяти).

Обновление: улучшена производительность за счет уменьшения объема памяти и улучшения поведения кэша. Можно выжать на 5-10% больше производительности, но увеличение сложности кода того не стоит. В конечном счете, этот алгоритм в первую очередь использует узкое место ЦП фон Неймана, и существует очень мало алгоритмических настроек, которые могут обойти это.

Также обновлен делитель производительности, так как код C ++ не предназначался для компиляции со всеми оптимизациями, и никто другой не делал этого. :)

Обновление 2: оптимизирована работа сита для улучшения доступа к памяти. Теперь обрабатывайте небольшие простые числа с помощью memcpy () (~ 5% ускорения) и пропускайте кратные 2, 3 и 5 при просеивании больших простых чисел (~ 10% ускорения).

Код C ++: 9,9 секунды (с g ++ 4,9)

Код Nimrod: 9,9 секунды (с -d: release, бэкэнд gcc 4.9)

proc handleSmallPrimes(sieve: var openarray[int32], m: int) =
  # Small primes are handled as a special case through what is ideally
  # the system's highly optimized memcpy() routine.
  let k = 2*3*5*7*11*13*17
  var sp = newSeq[int32](k div 2)
  for i in [3,5,7,11,13,17]:
    for j in countup(i, k, 2*i):
      sp[j div 2] = int32(i)
  for i in countup(0, sieve.high, len(sp)):
    if i + len(sp) <= len(sieve):
      copyMem(addr(sieve[i]), addr(sp[0]), sizeof(int32)*len(sp))
    else:
      copyMem(addr(sieve[i]), addr(sp[0]), sizeof(int32)*(len(sieve)-i))
  # Fixing up the numbers for values that are actually prime.
  for i in [3,5,7,11,13,17]:
    sieve[i div 2] = 0

proc constructSieve(m: int): seq[int32] =
  result = newSeq[int32](m div 2 + 1)
  handleSmallPrimes(result, m)
  var i = 19
  # Having handled small primes, we only consider candidates for
  # composite numbers that are relatively prime with 31. This cuts
  # their number almost in half.
  let steps = [ 1, 7, 11, 13, 17, 19, 23, 29, 31 ]
  var isteps: array[8, int]
  while i * i <= m:
    if result[i div 2] == 0:
      for j in 0..7: isteps[j] = i*(steps[j+1]-steps[j])
      var k = 1 # second entry in "steps mod 30" list.
      var j = 7*i
      while j <= m:
        result[j div 2] = int32(i)
        j += isteps[k]
        k = (k + 1) and 7 # "mod 30" list has eight elements.
    i += 2

proc calculateAndSumTotients(sieve: var openarray[int32], n: int): int =
  result = 1
  for i in 2'i32..int32(n):
    var tot: int32
    if (i and 1) == 0:
      var m = i div 2
      var pp: int32 = 2
      while (m and 1) == 0:
        pp *= 2
        m = m div 2
      if m == 1:
        tot = pp div 2
      else:
        tot = (pp div 2) * sieve[m div 2]
    elif sieve[i div 2] == 0: # prime?
      tot = i - 1
      sieve[i div 2] = tot
    else:
      # find and extract the first prime power pp.
      # It's relatively prime with i/pp.
      var p = sieve[i div 2]
      var m = i div p
      var pp = p
      while m mod p == 0 and m != p:
        pp *= p
        m = m div p
      if m == p: # is i a prime power?
        tot = pp*(p-1)
      else:
        tot = sieve[pp div 2] * sieve[m div 2]
      sieve[i div 2] = tot
    result += tot

proc main(n: int) =
  var sieve = constructSieve(n)
  let totSum = calculateAndSumTotients(sieve, n)
  echo totSum

main(580_000_000)
Реймер Берендс
источник
Эпическая! +1. Нимрод начинает
набирать
Подождите. Ого. Я поддерживаю ваш другой ответ. : P
cjfaure
1
Является ли Nimrod чем-то средним между Python и C?
mbomb007
Нимрод был недавно переименован в Ним; Хотя он заимствует синтаксический стиль Python, семантика отличается, и в отличие от C, он безопасен для памяти (если вы не используете небезопасные функции) и имеет сборку мусора.
Реймер Берендс
9

Java, оценка ~ 24 000 (360 000 000/15 000)

Приведенный ниже код Java выполняет вычисление суммарной функции и простого сита вместе. Обратите внимание, что в зависимости от вашей машины вы должны увеличить начальный / максимальный размер кучи (на моем довольно медленном ноутбуке мне пришлось подняться -Xmx3g -Xms3g).

public class Totient {

    final static int size = 360000000;
    final static int[] phi = new int[size];

    public static void main(String[] args) {
        long time = System.currentTimeMillis();
        long sum = 0;

        phi[1] = 1;
        for (int i = 2; i < size; i++) {
            if (phi[i] == 0) {
                phi[i] = i - 1;
                for (int j = 2; i * j < size; j++) {
                    if (phi[j] == 0)
                        continue;

                    int q = j;
                    int f = i - 1;
                    while (q % i == 0) {
                        f *= i;
                        q /= i;
                    }
                    phi[i * j] = f * phi[q];
                }
            }
            sum += phi[i];
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(sum);
    }
}
Говард
источник
9

Нимрод: ~ 2 333 333 (42 000 000 000/18 000)

Это использует совершенно другой подход от моего предыдущего ответа. Смотрите комментарии для деталей. longintМодуль можно найти здесь .

import longint

const max = 500_000_000

var ts_mem: array[1..max, int]

# ts(n, d) is defined as the number of pairs (a,b)
# such that 1 <= a <= b <= n and gcd(a,b) = d.
#
# The following equations hold:
#
# ts(n, d) = ts(n div d, 1)
# sum for i in 1..n of ts(n, i) = n*(n+1)/2
#
# This leads to the recurrence:
# ts(n, 1) = n*(n+1)/2 - sum for i in 2..n of ts(n, i)
#
# or, where ts(n) = ts(n, 1):
# ts(n) = n*(n+1)/2 - sum for i in 2..n of ts(n div i)
#
# Note that the large numbers that we deal with can
# overflow 64-bit integers.

proc ts(n, gcd: int): int =
  if n == 0:
    result = 0
  elif n == 1 and gcd == 1:
    result = 1
  elif gcd == 1:
    result = n*(n+1) div 2
    for i in 2..n:
      result -= ts(n, i)
  else:
    result = ts(n div gcd, 1)

# Below is the optimized version of the same algorithm.

proc ts(n: int): int =
  if n == 0:
    result = 0
  elif n == 1:
    result = 1
  else:
    if n <= max and ts_mem[n] > 0:
      return ts_mem[n]
    result = n*(n+1) div 2
    var p = n
    var k = 2
    while k < n div k:
      let pold = p
      p = n div k
      k += 1
      let t = ts(n div pold)
      result -= t * (pold-p)
    while p >= 2:
      result -= ts(n div p)
      p -= 1
    if n <= max:
      ts_mem[n] = result

proc ts(n: int128): int128 =
  if n <= 2_000_000_000:
    result = ts(n.toInt)
  else:
    result = n*(n+1) div 2
    var p = n
    var k = 2
    while k < n div k:
      let pold = p
      p = n div k
      k += 1
      let t = ts(n div pold)
      result = result - t * (pold-p)
    while p >= 2:
      result = result - ts(n div p)
      p = p - 1

echo ts(42_000_000_000.toInt128)
Реймер Берендс
источник
Дамы и господа, это то, что я называю волшебством.
Анна Джокела
2
Отличный подход для непосредственного расчета суммы, но, к сожалению, он не рассчитывает функцию сложения для стольких чисел, сколько вы можете, что является задачей, приведенной выше. Ваш код фактически вычисляет результаты (даже не результат функции totient) только для нескольких тысяч чисел (приблиз. 2*sqrt(n)), Что значительно снижает балл.
Говард
7

C #: 49 000 (980 000 000/20 000)

/codegolf//a/26800 "Код Говарда".
Но измененные значения фи вычисляются для нечетных целых чисел.

using System;
using sw = System.Diagnostics.Stopwatch;
class Program
{
    static void Main()
    {
        sw sw = sw.StartNew();
        Console.Write(sumPhi(980000000) + " " + sw.Elapsed);
        sw.Stop(); Console.Read();
    }

    static long sumPhi(int n)  // sum phi[i] , 1 <= i <= n
    {
        long s = 0; int[] phi;
        if (n < 1) return 0; phi = buildPhi(n + 1);
        for (int i = 1; i <= n; i++) s += getPhi(i, phi);
        return s;
    }

    static int getPhi(int i, int[] phi)
    {
        if ((i & 1) > 0) return phi[i >> 1];
        if ((i & 3) > 0) return phi[i >> 2];
        int z = ntz(i); return phi[i >> z >> 1] << z - 1;
    }

    static int[] buildPhi(int n)  // phi[i >> 1] , i odd , i < n
    {
        int i, j, y, x, q, r, f; int[] phi;
        if (n < 2) return new int[] { 0 };
        phi = new int[n / 2]; phi[0] = 1;
        for (j = 2, i = 3; i < n; i *= 3, j *= 3) phi[i >> 1] = j;
        for (x = 4, i = 5; i <= n >> 1; i += x ^= 6)
        {
            if (phi[i >> 1] > 0) continue; phi[i >> 1] = i ^ 1;
            for (j = 3, y = 3 * i; y < n; y += i << 1, j += 2)
            {
                if (phi[j >> 1] == 0) continue; q = j; f = i ^ 1;
                while ((r = q) == i * (q /= i)) f *= i;
                phi[y >> 1] = f * phi[r >> 1];
            }
        }
        for (; i < n; i += x ^= 6)  // primes > n / 2 
            if (phi[i >> 1] == 0)
                phi[i >> 1] = i ^ 1;
        return phi;
    }

    static int ntz(int i)  // number of trailing zeros
    {
        int z = 1;
        if ((i & 0xffff) == 0) { z += 16; i >>= 16; }
        if ((i & 0x00ff) == 0) { z += 08; i >>= 08; }
        if ((i & 0x000f) == 0) { z += 04; i >>= 04; }
        if ((i & 0x0003) == 0) { z += 02; i >>= 02; }
        return z - (i & 1);
    }
}

Новый счет: 61 000 (1 220 000 000/20 000)
В «App.config» мне пришлось добавить «gcAllowVeryLargeObjects enabled = true».

    static long sumPhi(int n)
    {
        int i1, i2, i3, i4, z; long s1, s2, s3, s4; int[] phi;
        if (n < 1) return 0; phi = buildPhi(n + 1); n -= 4; z = 2;
        i1 = 1; i2 = 2; i3 = 3; i4 = 4; s1 = s2 = s3 = s4 = 0;
        if (n > 0)
            for (; ; )
            {
                s1 += phi[i1 >> 1];
                s2 += phi[i2 >> 2];
                s3 += phi[i3 >> 1];
                s4 += phi[i4 >> z >> 1] << z - 1;
                i1 += 4; i2 += 4; i3 += 4; i4 += 4;
                n -= 4; if (n < 0) break;
                if (z == 2)
                {
                    z = 3; i4 >>= 3;
                    while ((i4 & 3) == 0) { i4 >>= 2; z += 2; }
                    z += i4 & 1 ^ 1;
                    i4 = i3 + 1;
                }
                else z = 2;
            }
        if (n > -4) s1 += phi[i1 >> 1];
        if (n > -3) s2 += phi[i2 >> 2];
        if (n > -2) s3 += phi[i3 >> 1];
        if (n > -1) s4 += phi[i4 >> z >> 1] << z - 1;
        return s1 + s2 + s3 + s4;
    }

    static int[] buildPhi(int n)
    {
        int i, j, y, x, q0, q1, f; int[] phi;
        if (n < 2) return new int[] { 0 };
        phi = new int[n / 2]; phi[0] = 1;
        for (uint u = 2, v = 3; v < n; v *= 3, u *= 3) phi[v >> 1] = (int)u;
        for (x = 4, i = 5; i <= n >> 1; i += x ^= 6)
        {
            if (phi[i >> 1] > 0) continue; phi[i >> 1] = i ^ 1;
            for (j = 3, y = 3 * i; y < n; y += i << 1, j += 2)
            {
                if (phi[j >> 1] == 0) continue; q0 = j; f = i ^ 1;
                while ((q1 = q0) == i * (q0 /= i)) f *= i;
                phi[y >> 1] = f * phi[q1 >> 1];
            }
        }
        for (; i < n; i += x ^= 6)
            if (phi[i >> 1] == 0)
                phi[i >> 1] = i ^ 1;
        return phi;
    }
P_P
источник
4

Python 3: ~ 24000 (335 000 000/14 000)

Моя версия представляет собой Python-порт алгоритма Говарда . Моя оригинальная функция была модификацией алгоритма, представленного в этом посте .

Я использую модули Numpy и Numba, чтобы ускорить время выполнения. Обратите внимание, что обычно вам не нужно объявлять типы локальных переменных (при использовании Numba), но в этом случае я хотел максимально сократить время выполнения.

Редактирование: объединяет функции constructsieve и summarum в одну функцию.

С ++: 9,99 (n = 14 000); Python 3: 9,94 (n = 335 000 000)

import numba as nb
import numpy as np
import time

n = 335000000

@nb.njit("i8(i4[:])", locals=dict(
    n=nb.int32, s=nb.int64, i=nb.int32,
    j=nb.int32, q=nb.int32, f=nb.int32))

def summarum(phi):
    s = 0

    phi[1] = 1

    i = 2
    while i < n:
        if phi[i] == 0:
            phi[i] = i - 1

            j = 2

            while j * i < n:
                if phi[j] != 0:
                    q = j
                    f = i - 1

                    while q % i == 0:
                        f *= i
                        q //= i

                    phi[i * j] = f * phi[q]
                j += 1
        s += phi[i]
        i += 1
    return s

if __name__ == "__main__":
    s1 = time.time()
    a = summarum(np.zeros(n, np.int32))
    s2 = time.time()

    print(a)
    print("{}s".format(s2 - s1))
Анна Йокела
источник
1
Вы должны отдать должное, когда копируете код от других пользователей.
Говард
Обновлено с правильными кредитами!
Анна Джокела
3

Вот моя реализация Python, которая, кажется, способна набрать ~ 60000 чисел за 10 секунд. Я делю числа на множители, используя алгоритм Полларда Ро и критерий простоты Рабина Миллера.

from Queue import Queue
import random

def gcd ( a , b ):
    while b != 0: a, b = b, a % b
    return a

def rabin_miller(p):
    if(p<2): return False
    if(p!=2 and p%2==0): return False
    s=p-1
    while(s%2==0): s>>=1
    for _ in xrange(10):
        a=random.randrange(p-1)+1
        temp=s
        mod=pow(a,temp,p)
        while(temp!=p-1 and mod!=1 and mod!=p-1):
            mod=(mod*mod)%p
            temp=temp*2
        if(mod!=p-1 and temp%2==0): return False
    return True

def pollard_rho(n):
    if(n%2==0): return 2;
    x=random.randrange(2,1000000)
    c=random.randrange(2,1000000)
    y=x
    d=1
    while(d==1):
        x=(x*x+c)%n
        y=(y*y+c)%n
        y=(y*y+c)%n
        d=gcd(x-y,n)
        if(d==n): break;
    return d;

def primeFactorization(n):
    if n <= 0: raise ValueError("Fucked up input, n <= 0")
    elif n == 1: return []
    queue = Queue()
    factors=[]
    queue.put(n)
    while(not queue.empty()):
        l=queue.get()
        if(rabin_miller(l)):
            factors.append(l)
            continue
        d=pollard_rho(l)
        if(d==l):queue.put(l)
        else:
            queue.put(d)
            queue.put(l/d)
    return factors

def phi(n):

    if rabin_miller(n): return n-1
    phi = n
    for p in set(primeFactorization(n)):
        phi -= (phi/p)
    return phi

if __name__ == '__main__':

  n = 1
  s = 0

  while n < 60000:
    n += 1
    s += phi(n)
  print(s)
will.fiset
источник
2

φ (2 n ) = 2 n - 1
Σ φ (2 i ) = 2 i - 1 для i от 1 до n

Во-первых, что-то найти раз:

import os
from time import perf_counter

SEARCH_LOWER = -1
SEARCH_HIGHER = 1

def integer_binary_search(start, lower=None, upper=None, big_jump=1):
    if lower is not None and lower == upper:
        raise StopIteration # ?

    result = yield start

    if result == SEARCH_LOWER:
        if lower is None:
            yield from integer_binary_search(
                start=start - big_jump,
                lower=None,
                upper=start - 1,
                big_jump=big_jump * 2)
        else:
            yield from integer_binary_search(
                start=(lower + start) // 2,
                lower=lower,
                upper=start - 1)
    elif result == SEARCH_HIGHER:
        if upper is None:
            yield from integer_binary_search(
                start=start + big_jump,
                lower=start + 1,
                upper=None,
                big_jump=big_jump * 2)
        else:
            yield from integer_binary_search(
                start=(start + upper) // 2,
                lower=start + 1,
                upper=upper)
    else:
        raise ValueError('Expected SEARCH_LOWER or SEARCH_HIGHER.')

search = integer_binary_search(start=1000, lower=1, upper=None, big_jump=2500)
n = search.send(None)

while True:
    print('Trying with %d iterations.' % (n,))

    os.spawnlp(
        os.P_WAIT,
        'g++', 'g++', '-Wall', '-Wextra', '-pedantic', '-O0', '-o', 'reference',
        '-DITERATIONS=%d' % (n,),
        'reference.cpp')

    start = perf_counter()
    os.spawnl(os.P_WAIT, './reference', './reference')
    end = perf_counter()
    t = end - start

    if t >= 10.1:
        n = search.send(SEARCH_LOWER)
    elif t <= 9.9:
        n = search.send(SEARCH_HIGHER)
    else:
        print('%d iterations in %f seconds!' % (n, t))
        break

Для справочного кода, для меня это:

...
Попытка с 14593 итерациями.
64724364 14593
итераций за 9,987747 секунд!

Теперь Хаскелл:

import System.Environment (getArgs)

phiSum :: Integer -> Integer
phiSum n = 2 ^ n - 1

main :: IO ()
main = getArgs >>= print . phiSum . (2^) . read . head

Это делает что-то с 2525224 цифрами за 0,718 секунды. И теперь я просто замечаю комментарий @ Говарда.

Рыбаковым
источник
Можете ли вы опубликовать счет с количеством последовательных чисел, начиная с 1, который вы суммировали?
QWR
@qwr, это было бы 0. Если вы хотите последовательные числа, вы должны указать это в своем вопросе =)
Ry-
Я сделал. Я уже редактировал, я буду редактировать снова.
QWR
2

Matlab: 1464 = 26355867/18000

Я не могу проверить ваш код, поэтому я разделил на 18000, так как он представляет собой самый быстрый компьютер из тех, кто тестировал. Я пришел на счет, используя это свойство:

  • если p простое число, phi (p) = p - 1 (для p <10 ^ 20)

Мне больше всего нравится, что это один лайнер:

sum(primes(500000000)-1)
Деннис Джаэруддин
источник
1
Как насчет phi(p)всех не премьер p?
Geobits
2
@Geobits Я пропустил их, так как в вопросе не указано, какие числа вы должны использовать, если они действительно рассчитаны.
Деннис Джаэруддин
Ах, не заметил этого в формулировке. Ницца.
Geobits
Вы даже не опубликовали счет ...
qwr
1
... Как можно не использовать Matlab & C ++ на одном компьютере?
Кайл Канос
1

Python 2.7: 10.999 (165975/15090)

Pypy 2.3.1: 28.496 (430000/15090)

Некоторые интересные методы, которые я использую:

Тест Рабина-Миллера Стронга с псевдопримаром - тест простоты, который обеспечивает эффективный вероятностный алгоритм для определения, является ли данное число простым

Формула произведения Эйлера - произведение на различные простые числа, делящие n

Формула продукта Эйлера

Код:

import math
import random

#perform a Modular exponentiation
def modular_pow(base, exponent, modulus):
    result=1
    while exponent>0:
        if exponent%2==1:
           result=(result * base)%modulus
        exponent=exponent>>1
        base=(base * base)%modulus
    return result

#Miller-Rabin primality test
def checkMillerRabin(n,k):
    if n==2: return True
    if n==1 or n%2==0: return False

    #find s and d, with d odd
    s=0
    d=n-1
    while(d%2==0):
        d/=2
        s+=1
    assert (2**s*d==n-1)

    #witness loop
    composite=1
    for i in xrange(k):
        a=random.randint(2,n-1)
        x=modular_pow(a,d,n)
        if x==1 or x==n-1: continue
        for j in xrange(s-1):
            composite=1
            x=modular_pow(x,2,n)
            if x==1: return False #is composite
            if x==n-1: 
                composite=0
                break
        if composite==1:
            return False        #is composite
    return True                 #is probably prime

def findPrimes(n):              #generate a list of primes, using the sieve of eratosthenes

    primes=(n+2)*[True]

    for i in range(2,int(math.sqrt(n))+1):
        if primes[i]==True:
            for j in range(i**2,n+1,i):
                primes[j]=False

    primes=[i for i in range(2,len(primes)-1) if primes[i]==True]
    return primes

def primeFactorization(n,primes):   #find the factors of a number

    factors=[]

    i=0
    while(n!=1):
        if(n%primes[i]==0):
            factors.append(primes[i])
            n/=primes[i]
        else:
            i+=1

    return factors

def phi(n,primes):
    #some useful properties
    if (checkMillerRabin(n,10)==True):      #fast prime check
        return n-1

    factors=primeFactorization(n,primes)    #prime factors
    distinctive_prime_factors=set(factors)  

    totient=n
    for f in distinctive_prime_factors:     #phi = n * sum (1 - 1/p), p is a distinctive prime factor
        totient*=(1-1.0/f);

    return totient

if __name__ == '__main__':


    s=0
    N=165975
    # N=430000
    primes=findPrimes(N)    #upper bound for the number of primes
    for i in xrange(1,N):
        s+=phi(i,primes)

    print "Sum =",s 
AlexPnt
источник
Спасибо за алгоритмы! Это было единственное, что я мог легко понять, и это не проверка грубой силы подсчета сопряжения.
пользователь