Эффективный способ найти делимость - PullRequest
0 голосов
/ 02 ноября 2018

Профессор говорит, что это не эффективный алгоритм проверки, делится ли число на число от 100 000 до 150 000. У меня проблемы с поиском лучшего способа. Любая помощь будет оценена.

unsigned short divisibility_check(unsigned long n) {
    unsigned long i;
    for (i = 100000; i <= 150000; i++) {
        if (n % i == 0) {
            return 0;
        }
    }
    return 1;
}

Ответы [ 5 ]

0 голосов
/ 04 ноября 2018

Вот очень простое решение с ситовым кешем. Если вы вызываете функцию divisibility_check для многих чисел в последовательности, это должно быть очень эффективным:

#include <string.h>

int divisibility_check_sieve(unsigned long n) {
    static unsigned long sieve_min = 1, sieve_max;
    static unsigned char sieve[1 << 19]; /* 1/2 megabyte */
    if (n < sieve_min || n > sieve_max) {
        sieve_min = n & ~(sizeof(sieve) - 1);
        sieve_max = sieve_min + sizeof(sieve) - 1;
        memset(sieve, 1, sizeof sieve);
        for (unsigned long m = 100000; m <= 150000; m++) {
            unsigned long i = sieve_min % m;
            if (i != 0)
                i = m - i;
            for (; i < sizeof sieve; i += m) {
                sieve[i] = 0;
            }
        }
    }
    return sieve[n - sieve_min];
}

Вот сравнительный тест:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int divisibility_check_naive(unsigned long n) {
    for (unsigned long i = 100000; i <= 150000; i++) {
        if (n % i == 0) {
            return 0;
        }
    }
    return 1;
}

int divisibility_check_small(unsigned long n) {
    unsigned long i, min = n / 150000, max = n / 100000;
    min += (min == 0);
    max += (max == 0);
    if (max - min > 150000 - 100000) {
        for (i = 100000; i <= 150000; i++) {
            if (n % i == 0) {
                return 0;
            }
        }
        return 1;
    } else {
        for (i = min; i <= max; i++) {
            if (n % i == 0) {
                unsigned long div = n / i;
                if (div >= 100000 && div <= 150000)
                    return 0;
            }
        }
        return 1;
    }
}

int divisibility_check_sieve(unsigned long n) {
    static unsigned long sieve_min = 1, sieve_max;
    static unsigned char sieve[1 << 19]; /* 1/2 megabyte */
    if (n < sieve_min || n > sieve_max) {
        sieve_min = n & ~(sizeof(sieve) - 1);
        sieve_max = sieve_min + sizeof(sieve) - 1;
        memset(sieve, 1, sizeof sieve);
        for (unsigned long m = 100000; m <= 150000; m++) {
            unsigned long i = sieve_min % m;
            if (i != 0)
                i = m - i;
            for (; i < sizeof sieve; i += m) {
                sieve[i] = 0;
            }
        }
    }
    return sieve[n - sieve_min];
}

int main(int argc, char *argv[]) {
    unsigned long n, count = 0, lmin, lmax, range[2] = { 1, 500000 };
    int pos = 0, naive = 0, small = 0, sieve = 1;
    clock_t t;
    char *p;

    for (int i = 1; i < argc; i++) {
        n = strtoul(argv[i], &p, 0);
        if (*p == '\0' && pos < 2)
            range[pos++] = n;
        else if (!strcmp(argv[i], "naive"))
            naive = 1;
        else if (!strcmp(argv[i], "small"))
            small = 1;
        else if (!strcmp(argv[i], "sieve"))
            sieve = 1;
        else
            printf("invalid argument: %s\n", argv[i]);
    }
    lmin = range[0];
    lmax = range[1] + 1;
    if (naive) {
        t = clock();
        for (count = 0, n = lmin; n != lmax; n++) {
            count += divisibility_check_naive(n);
        }
        t = clock() - t;
        printf("naive: [%lu..%lu] -> %lu non-divisible numbers, %10.2fms\n",
               lmin, lmax - 1, count, t * 1000.0 / CLOCKS_PER_SEC);
    }
    if (small) {
        t = clock();
        for (count = 0, n = lmin; n != lmax; n++) {
            count += divisibility_check_small(n);
        }
        t = clock() - t;
        printf("small: [%lu..%lu] -> %lu non-divisible numbers, %10.2fms\n",
               lmin, lmax - 1, count, t * 1000.0 / CLOCKS_PER_SEC);
    }
    if (sieve) {
        t = clock();
        for (count = 0, n = lmin; n != lmax; n++) {
            count += divisibility_check_sieve(n);
        }
        t = clock() - t;
        printf("sieve: [%lu..%lu] -> %lu non-divisible numbers, %10.2fms\n",
               lmin, lmax - 1, count, t * 1000.0 / CLOCKS_PER_SEC);
    }
    return 0;
}

Вот некоторые времена выполнения:

naive: [1..500000] -> 329164 non-divisible numbers,  158174.52ms
small: [1..500000] -> 329164 non-divisible numbers,      12.62ms
sieve: [1..500000] -> 329164 non-divisible numbers,       1.35ms
sieve: [0..4294967295] -> 3279784841 non-divisible numbers,    8787.23ms
sieve: [10000000000000000000..10000000001000000000] -> 765978176 non-divisible numbers,    2205.36ms
0 голосов
/ 04 ноября 2018

Для сравнения, вот что я имел в виду, когда отправлял свой комментарий об использовании простой факторизации. Скомпилированный с gcc -std=c99 -O3 -m64 -march=haswell это немного быстрее, чем простой метод с проверками и инверсией при тестировании с последними 10000 целыми числами в 64-битном диапазоне (3,469 против 3,624 секунды).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

void eratosthenes(bool *ptr, uint64_t size) {
    memset(ptr, true, size);
    for (uint64_t i = 2; i * i < size; i++) {
        if (ptr[i]) {
            for (uint64_t j = i * i; j < size; j += i) {
                ptr[j] = false;
            }
        }
    }
}

bool divisible(uint64_t n, uint64_t a, uint64_t b) {
    /* check for trivial cases first                                    */
    if (n < a) {
        return false;
    }
    if (n <= b) {
        return true;
    }
    if (n < 2 * a) {
        return false;
    }

    /* Inversion: use range n/b ~ n/a; see Nominal Animal's answer      */
    if (n < a * b) {
        uint64_t c = a;
        a = (n + b - 1) / b; // n/b rounded up
        b = n / c;
    }

    /* Create prime sieve when first called, or re-calculate it when    */
    /* called with a higher value of b; place before inversion in case  */
    /* of a large sequential test, to avoid repeated re-calculation.    */
    static bool *prime = NULL;
    static uint64_t prime_size = 0;
    if (prime_size <= b) {
        prime_size = b + 1;
        prime = realloc(prime, prime_size * sizeof(bool));
        if (!prime) {
            printf("Out of memory!\n");
            return false;
        }
        eratosthenes(prime, prime_size);
    }

    /* Factorize n into prime factors up to b, using trial division;    */
    /* there are more efficient but also more complex ways to do this.  */
    /* You could return here, if a factor in the range a~b is found.    */
    static uint64_t factor[63];
    uint8_t factors = 0;
    for (uint64_t i = 2; i <= n && i <= b; i++) {
        if (prime[i]) {
            while (n % i == 0) {
                factor[factors++] = i;
                n /= i;
            }
        }
    }

    /* Prepare divisor sieve when first called, or re-allocate it when  */
    /* called with a higher value of b; in a higher-level language, you */
    /* would probably use a different data structure for this, because  */
    /* this method iterates repeatedly over a potentially sparse array. */
    static bool *divisor = NULL;
    static uint64_t div_size = 0;
    if (div_size <= b / 2) {
        div_size = b / 2 + 1;
        divisor = realloc(divisor, div_size * sizeof(bool));
        if (!divisor) {
            printf("Out of memory!\n");
            return false;
        }
    }
    memset(divisor, false, div_size);
    divisor[1] = true;
    uint64_t max = 1;

    /* Iterate over each prime factor, and for every divisor already in */
    /* the sieve, add the product of the divisor and the factor, up to  */
    /* the value b/2. If the product is in the range a~b, return true.  */
    for (uint8_t i = 0; i < factors; i++) {
        for (uint64_t j = max; j > 0; j--) {
            if (divisor[j]) {
                uint64_t product = factor[i] * j;
                if (product >= a && product <= b) {
                    return true;
                }
                if (product < div_size) {
                    divisor[product] = true;
                    if (product > max) {
                        max = product;
                    }
                }
            }
        }
    }
    return false;
}

int main() {
    uint64_t count = 0;
    for (uint64_t n = 18446744073709541615LLU; n <= 18446744073709551614LLU; n++) {
        if (divisible(n, 100000, 150000)) ++count;
    }
    printf("%llu", count);
    return 0;
}

И это реализация наивного + чека + инверсии, с которой я сравнил:

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

bool divisible(uint64_t n, uint64_t a, uint64_t b) {
    if (n < a) {
        return false;
    }
    if (n <= b) {
        return true;
    }
    if (n < 2 * a) {
        return false;
    }
    if (n < a * b) {
        uint64_t c = a;
        a = (n + b - 1) / b;
        b = n  / c;
    }
    while (a <= b) {
        if (n % a++ == 0) return true;
    }
    return false;
}

int main() {
    uint64_t count = 0;
    for (uint64_t n = 18446744073709541615LLU; n <= 18446744073709551614LLU; n++) {
        if (divisible(n, 100000, 150000)) ++count;
    }
    printf("%llu", count);
    return 0;
}
0 голосов
/ 03 ноября 2018

Допустим, вам нужно выяснить, делится ли положительное целое число K на число от 100 000 до 150 000, и это настолько редкая операция, что выполнение предварительных вычислений просто не стоит процессорного времени или памяти б.

Если K <100 000, то оно не может быть кратно числу от 100 000 до 150 000. </p>

Если 100 000 ≤ K ≤ 150 000, оно делится само по себе. Вам решать, будет ли это иметь значение.

Чтобы K > 150 000 делилось на M , при 100 000 ≤ M ≤ 150 000, K также должно делиться L = K / M . Это потому, что K = L × M , и все три являются натуральными числами. Таким образом, вам нужно только проверить делимость K на набор L , где ⌊ K / 150000 ⌋ ≤ L ≤ 10 K / 100 000 ⌋.

Однако этот набор L с становится больше, чем набор возможных M с, когда K> = 15 000 000 000. Тогда опять же меньше работы, чтобы просто проверить K на делимость на каждый M , так же, как сейчас код OP.


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

В этом конкретном случае вам, вероятно, следует добавить комментарий к каждому предложению if, поясняющий ваши рассуждения, так же, как я это делал выше.

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

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

Даже в упражнениях правильные комментарии могут быть разницей между "нет баллов, это не работает" и "хорошо, я частично дам вам кредит, потому что вы в строке N была опечатка, ошибка, но в противном случае ваше решение сработало бы ".


Поскольку bolov не понимает, как вышеприведенное приводит к функции "naive_with_checks", я покажу, как она реализована здесь.

Для удобства тестирования я покажу полную программу тестирования. В качестве параметров программы укажите диапазон целых чисел и диапазон принятых делителей (т. Е. thisprogram 1 500000 100000 150000 для дублирования тестов Болова).

#include <stdlib.h>
#include <inttypes.h>
#include <limits.h>
#include <locale.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>

int is_divisible(const uint64_t  number,
                 const uint64_t  minimum_divisor,
                 const uint64_t  maximum_divisor)
{
    uint64_t divisor, minimum_result, maximum_result, result;

    if (number < minimum_divisor) {
        return 0;
    }

    if (number <= maximum_divisor) {
        /* Number itself is a valid divisor. */
        return 1;
    }

    minimum_result = number / maximum_divisor;
    if (minimum_result < 2) {
        minimum_result = 2;
    }

    maximum_result = number / minimum_divisor;
    if (maximum_result < minimum_result) {
        maximum_result = minimum_result;
    }

    if (maximum_result - minimum_result > maximum_divisor - minimum_divisor) {
        /* The number is so large that it is the least amount of work
           to check each possible divisor. */
        for (divisor = minimum_divisor; divisor <= maximum_divisor; divisor++) {
            if (number % divisor == 0) {
                return 1;
            }
        }

        return 0;

    } else {
        /* There are fewer possible results than divisors,
           so we check the results instead. */

        for (result = minimum_result; result <= maximum_result; result++) {
            if (number % result == 0) {
                divisor = number / result;
                if (divisor >= minimum_divisor && divisor <= maximum_divisor) {
                    return 1;
                }
            }
        }

        return 0;
    }
}

int parse_u64(const char *s, uint64_t *to)
{
    unsigned long long  value;
    const char         *end;

    /* Empty strings are not valid. */
    if (s == NULL || *s == '\0')
        return -1;

    /* Parse as unsigned long long. */
    end = s;
    errno = 0;
    value = strtoull(s, (char **)(&end), 0);
    if (errno == ERANGE)
        return -1;
    if (end == s)
        return -1;

    /* Overflow? */
    if (value > UINT64_MAX)
        return -1;

    /* Skip trailing whitespace. */
    while (isspace((unsigned char)(*end)))
        end++;

    /* If the string does not end here, it has garbage in it. */
    if (*end != '\0')
        return -1;

    if (to)
        *to = (uint64_t)value;

    return 0;
}

int main(int argc, char *argv[])
{
    uint64_t kmin, kmax, dmin, dmax, k, count;

    if (argc != 5) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help | help ]\n", argv[0]);
        fprintf(stderr, "       %s MIN MAX MIN_DIVISOR MAX_DIVISOR\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program counts which positive integers between MIN and MAX,\n");
        fprintf(stderr, "inclusive, are divisible by MIN_DIVISOR to MAX_DIVISOR, inclusive.\n");
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    /* Use current locale. This may change which codes isspace() considers whitespace. */
    if (setlocale(LC_ALL, "") == NULL)
        fprintf(stderr, "Warning: Your C library does not support your current locale.\n");

    if (parse_u64(argv[1], &kmin) || kmin < 1) {
        fprintf(stderr, "%s: Invalid minimum positive integer to test.\n", argv[1]);
        return EXIT_FAILURE;
    }
    if (parse_u64(argv[2], &kmax) || kmax < kmin || kmax >= UINT64_MAX) {
        fprintf(stderr, "%s: Invalid maximum positive integer to test.\n", argv[2]);
        return EXIT_FAILURE;
    }
    if (parse_u64(argv[3], &dmin) || dmin < 2) {
        fprintf(stderr, "%s: Invalid minimum divisor to test for.\n", argv[3]);
        return EXIT_FAILURE;
    }
    if (parse_u64(argv[4], &dmax) || dmax < dmin) {
        fprintf(stderr, "%s: Invalid maximum divisor to test for.\n", argv[4]);
        return EXIT_FAILURE;
    }

    count = 0;
    for (k = kmin; k <= kmax; k++)
        count += is_divisible(k, dmin, dmax);

    printf("%" PRIu64 "\n", count);
    return EXIT_SUCCESS;
}

Полезно отметить, что вышеприведенный тест Болова, т. Е. thisprogram 1 500000 100000 150000, занимает всего около 15 мс времени настенных часов (13 мс времени процессора), в среднем, на более медленном процессоре Core i5-7200U. Для действительно больших чисел, например от 280 000 000 000 до 280 000 010 000, тест выполняет максимальный объем работы и занимает около 3,5 секунд на 10 000 номеров на этом компьютере.

Другими словами, я бы не поверил, что числа Болова имеют какое-либо отношение к временам для правильно написанных тестовых случаев.

важноотметить, что для любого K от 1 до 500 000, того же теста, который Болов говорит, что их код измеряет, приведенный выше код выполняет не более двух тестов делимости, чтобы найти, если K делится на целое число от 100 000 до 150 000.

Следовательно, это решение весьма эффективно. Это определенно приемлемо и почти оптимально, когда проверенные значения K относительно малы (скажем, 32-разрядные целые числа без знака или меньше) или когда нельзя использовать предварительно вычисленные таблицы.

Даже если можно использовать предварительно вычисленные таблицы, неясно, станет ли / когда простое разложение быстрее, чем прямые проверки. Несомненно, существует компромисс в размере и содержании предварительно вычисленных таблиц. Болов утверждает, что он явно превосходит другие методы, но не реализовал надлежащий «наивный» тест делимости, как показано выше, и основывает свое мнение на экспериментах с довольно маленькими целыми числами (от 1 до 500 000), которые имеют простые простые разложения.

Например, таблица целых чисел от 1 до 500 000, предварительно проверенная на делимость, занимает всего 62500 байт (43750 байт для 150 000 - 500 000). С этой таблицей каждый тест занимает небольшое почти постоянное время (которое зависит только от эффектов памяти и кэша). Расширение его на все 32-разрядные целые числа без знака потребовало бы 512 ГиБ (536 870 912 байт); таблица может храниться в отображаемом в памяти файле, доступном только для чтения, чтобы позволить ядру ОС в любое время управлять тем, сколько из него отображается в ОЗУ.

Сама первичная декомпозиция, особенно с использованием пробного деления, становится дороже, чем наивный подход, когда число пробных делений превышает диапазон возможных делителей (50 000 делителей в данном конкретном случае). Поскольку между 1 и 150 000 имеется 13848 простых чисел (если считать 1 и 2 простыми числами), число пробных делений может легко приблизиться к числу делителей для достаточно больших входных значений.

Для чисел со многими простыми факторами комбинаторная фаза, нахождение того, умножается ли любое подмножество простых факторов на число между 100 000 и 150 000, еще более проблематично. Количество возможных комбинаций растет быстрее, чем в геометрической прогрессии. Без тщательных проверок одна только эта фаза может выполнить намного больше работы на большое количество входных данных, чем просто пробное деление с каждым возможным делителем.

(Например, если у вас есть 16 различных простых факторов, у вас уже есть 65 535 различных комбинаций; это больше, чем количество прямых пробных делений. Однако все такие числа больше, чем 64-битные; наименьшее - 2 · 3 · 5 · 7 · 11 · 13 · 17 · 19 · 23 · 29 · 31 · 37 · 41 · 43 · 47 · 53 = 32 589 158 477 190 044 730, что является 65-разрядным числом.)

Существует также проблема сложности кода. Чем сложнее код, тем сложнее его отладить и поддерживать.

0 голосов
/ 03 ноября 2018

Хорошо, поэтому я реализовал версию с простыми ситами и факторизацией, упомянутую в комментариях m69, и она ... намного быстрее, чем наивный подход. Должен признать, я этого не ожидал.

Мои записи: left == 100'000 и right = 150'000

  • наивный ваша версия
  • naive_with_checks ваша версия с простыми проверками:

    • if (n < left) без делителя
    • else if (n <= right) делитель
    • else if (left * 2 >= right && n < left * 2) делитель
  • факторизация (вышеуказанные проверки выполнены)

    1. Предварительно вычислить Сито Эратосфена для всех простых чисел до right. Это время не измеряется
    2. factorize n (только с простыми числами из предыдущего шага)
    3. сгенерируйте все подмножества (обратный поиск, сначала глубина: то есть сначала сгенерируйте p1^0 * p2^0 * p3^0 вместо p1^5 сначала) с продуктом < left или до тех пор, пока продукт не окажется в [left, right] (найденный делитель).
  • factorization_opt оптимизация предыдущего алгоритма, когда подмножества не генерируются (вектор подмножеств не создается). Я просто передаю текущий продукт от одной итерации возврата к следующей.

  • Версия Nominal Animal Я также запустил его версию в моей системе с тем же диапазоном.

Я написал программу на C++, поэтому я не буду здесь ею делиться.

Я использовал std::uint64_t в качестве типа данных и проверил все числа от 1 до 500'000, чтобы посмотреть, делится ли каждое на число в интервале [100'000, 150'000]. Все версии достигли одного и того же решения: 170'836 числа с положительными результатами.

Настройка:

  • Аппаратное обеспечение: Intel Core i7-920 , 4 ядра с HT (все версии алгоритма однопоточные), 2,66 ГГц (прирост 2,93 ГГц), 8 МБ SmartCache; Память: 6 ГБ, трехканальный DDR3.

  • Компилятор: Visual Studio 2017 (v141), режим выпуска x64.

Я также должен добавить, что я не профилировал программы, так что определенно есть место для улучшения реализации. Однако этого достаточно, поскольку идея состоит в том, чтобы найти лучший алгоритм.

version                |  elapsed time (milliseconds)
-----------------------+--------------
naive                  |  167'378 ms (yes, it's thousands separator, aka 167 seconds)
naive_with_checks      |   97'197 ms
factorization          |    7'906 ms
factorization_opt      |    7'320 ms
                       |
Nominal Animal version |       14 ms

Некоторый анализ:

Для наивных против naive_with_checks: все числа в [1 200'000] могут быть решены с помощью простых проверок. Поскольку они представляют 40% всех проверенных чисел, версия naive_with_checks выполняет примерно 60% работы, выполняемой наивным. Время выполнения отражает это, так как время выполнения naive_with_checks составляет ≅58% от простой версии.

Версия факторизации работает в 12,3 раза быстрее. Это действительно впечатляет. Я не анализировал временную сложность алгоритма.

И окончательная оптимизация приносит дополнительное ускорение в 1,08 раза. Это в основном время, полученное за счет удаления создания и копирования небольших векторов подмножеств факторов.

Для тех, кто заинтересован, предварительный расчет сита, который не включен выше, занимает около 1 ms. И это наивная реализация из Википедии, никаких оптимизаций.

0 голосов
/ 02 ноября 2018

Вот рекурсивный метод с простыми числами. Идея здесь состоит в том, что если число делится на число между 100000 и 150000, существует путь уменьшения путем деления произведения только соответствующих простых чисел, которые пройдут через состояние в целевом диапазоне. (Примечание: приведенный ниже код предназначен для чисел больше 100000 * 150000). В моем тестировании я не смог найти экземпляр, где стек выполнил более 600 итераций.

# Euler sieve
def getPrimes():
  n = 150000
  a = (n+1) * [None]
  ps = ([],[])
  s = []
  p = 1

  while (p < n):
    p = p + 1

    if not a[p]:
      s.append(p)
      # Save primes less
      # than half
      # of 150000, the only
      # ones needed to construct
      # our candidates.
      if p < 75000:
        ps[0].append(p);
      # Save primes between
      # 100000 and 150000
      # in case our candidate
      # is prime.
      elif p > 100000:
        ps[1].append(p)

      limit = n / p
      new_s = []

      for i in s:
        j = i
        while j <= limit:
          new_s.append(j)
          a[j*p] = True
          j = j * p
      s = new_s

  return ps

ps1, ps2 = getPrimes()

def f(n):
  # Prime candidate
  for p in ps2:
    if not (n % p):
      return True

  # (primes, prime_counts)
  ds = ([],[])
  prod = 1
  # Prepare only prime
  # factors that could
  # construct a composite
  # candidate.
  for p in ps1:
    while not (n % p):
      prod *= p
      if (not ds[0] or ds[0][-1] != p):
        ds[0].append(p)
        ds[1].append(1)
      else:
        ds[1][-1] += 1
      n /= p

  # Reduce the primes product to
  # a state where it's between
  # our target range.
  stack = [(prod,0)]

  while stack:
    prod, i = stack.pop()

    # No point in reducing further
    if prod < 100000:
      continue
    # Exit early
    elif prod <= 150000:
      return True
    # Try reducing the product
    # by different prime powers
    # one prime at a time
    if i < len(ds[0]):
      for p in xrange(ds[1][i] + 1):
        stack.append((prod / ds[0][i]**p, i + 1))

  return False

Выход:

c = 0

for ii in xrange(1099511627776, 1099511628776):
  f_i = f(ii)

  if f_i:
    c += 1

print c # 239
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...