Как найти количество простых чисел в диапазоне

Given a range [L, R], we need to find the count of total numbers of prime numbers in the range [L, R] where 0 <= L <= R < 10000. Consider that there are a large number of queries for different ranges.
Examples: 
 

Input : Query 1 : L = 1, R = 10
        Query 2 : L = 5, R = 10
Output : 4
         2
Explanation
Primes in the range L = 1 to R = 10 are 
{2, 3, 5, 7}. Therefore for query, answer 
is 4 {2, 3, 5, 7}.
For the second query, answer is 2 {5, 7}.

A simple solution is to do the following for every query [L, R]. Traverse from L to R, check if current number is prime. If yes, increment the count. Finally, return the count.
An efficient solution is to use Sieve of Eratosthenes to find all primes up to the given limit. Then we compute a prefix array to store counts till every value before limit. Once we have a prefix array, we can answer queries in O(1) time. We just need to return prefix[R] – prefix[L-1]. 
 

C++

#include <bits/stdc++.h>

using namespace std;

const int MAX = 10000;

int prefix[MAX + 1];

void buildPrefix()

{

    bool prime[MAX + 1];

    memset(prime, true, sizeof(prime));

    for (int p = 2; p * p <= MAX; p++) {

        if (prime[p] == true) {

            for (int i = p * 2; i <= MAX; i += p)

                prime[i] = false;

        }

    }

    prefix[0] = prefix[1] = 0;

    for (int p = 2; p <= MAX; p++) {

        prefix[p] = prefix[p - 1];

        if (prime[p])

            prefix[p]++;

    }

}

int query(int L, int R)

{

    return prefix[R] - prefix[L - 1];

}

int main()

{

    buildPrefix();

    int L = 5, R = 10;

    cout << query(L, R) << endl;

    L = 1, R = 10;

    cout << query(L, R) << endl;

    return 0;

}

Java

import java.util.*;

class GFG {

static final int MAX = 10000;

static int prefix[] = new int[MAX + 1];

static void buildPrefix() {

    boolean prime[] = new boolean[MAX + 1];

    Arrays.fill(prime, true);

    for (int p = 2; p * p <= MAX; p++) {

    if (prime[p] == true) {

        for (int i = p * 2; i <= MAX; i += p)

        prime[i] = false;

    }

    }

    prefix[0] = prefix[1] = 0;

    for (int p = 2; p <= MAX; p++) {

    prefix[p] = prefix[p - 1];

    if (prime[p])

        prefix[p]++;

    }

}

static int query(int L, int R)

{

    return prefix[R] - prefix[L - 1];

}

public static void main(String[] args) {

    buildPrefix();

    int L = 5, R = 10;

    System.out.println(query(L, R));

    L = 1; R = 10;

    System.out.println(query(L, R));

}

}

Python3

MAX = 10000

prefix =[0]*(MAX + 1)

def buildPrefix():

    prime = [1]*(MAX + 1)

    p = 2

    while(p * p <= MAX):

        if (prime[p] == 1):

            i = p * 2

            while(i <= MAX):

                prime[i] = 0

                i += p

        p+=1

    for p in range(2,MAX+1):

        prefix[p] = prefix[p - 1]

        if (prime[p]==1):

            prefix[p]+=1

def query(L, R):

    return prefix[R]-prefix[L - 1]

if __name__=='__main__':

    buildPrefix()

    L = 5

    R = 10

    print(query(L, R))

    L = 1

    R = 10

    print(query(L, R))

C#

using System;

class GFG

{

static int MAX = 10000;

static int[] prefix = new int[MAX + 1];

static void buildPrefix()

{

    bool[] prime = new bool[MAX + 1];

    for (int p = 2;

             p * p <= MAX; p++)

    {

    if (prime[p] == false)

    {

        for (int i = p * 2;

                 i <= MAX; i += p)

        prime[i] = true;

    }

    }

    prefix[0] = prefix[1] = 0;

    for (int p = 2; p <= MAX; p++)

    {

        prefix[p] = prefix[p - 1];

        if (prime[p] == false)

            prefix[p]++;

    }

}

static int query(int L, int R)

{

    return prefix[R] -

           prefix[L - 1];

}

public static void Main()

{

    buildPrefix();

    int L = 5, R = 10;

    Console.WriteLine(query(L, R));

    L = 1; R = 10;

    Console.WriteLine(query(L, R));

}

}

PHP

<?php

$MAX = 10000;

$prefix = array_fill(0, ($MAX + 1), 0);

function buildPrefix()

{

    global $MAX, $prefix;

    $prime = array_fill(0, ($MAX + 1), true);

    for ($p = 2;

         $p * $p <= $MAX; $p++)

    {

        if ($prime[$p] == true)

        {

            for ($i = $p * 2;

                 $i <= $MAX; $i += $p)

                $prime[$i] = false;

        }

    }

    for ($p = 2; $p <= $MAX; $p++)

    {

        $prefix[$p] = $prefix[$p - 1];

        if ($prime[$p])

            $prefix[$p]++;

    }

}

function query($L, $R)

{

    global $prefix;

    return $prefix[$R] -

           $prefix[$L - 1];

}

buildPrefix();

$L = 5;

$R = 10;

echo query($L, $R) . "n";

$L = 1;

$R = 10;

echo query($L, $R) . "n";

?>

Javascript

<script>

let MAX = 10000;

let prefix = [];

function buildPrefix() {

    let prime = [];

    for (let p = 1; p <= MAX +1; p++) {

        prime[p] = true;

    }

    for (let p = 2; p * p <= MAX; p++) {

    if (prime[p] == true) {

        for (let i = p * 2; i <= MAX; i += p)

        prime[i] = false;

    }

    }

    prefix[0] = prefix[1] = 0;

    for (let p = 2; p <= MAX; p++) {

    prefix[p] = prefix[p - 1];

    if (prime[p])

        prefix[p]++;

    }

}

function query(L, R)

{

    return prefix[R] - prefix[L - 1];

}

    buildPrefix();

    let L = 5, R = 10;

    document.write(query(L, R) + "<br/>");

    L = 1; R = 10;

    document.write(query(L, R));

</script>

Output:  

2
4

Time Complexity: O(n*log(log(n)))

Auxiliary Space: O(n)

Here, n is the size of the prime array, Which is MAX here

This article is contributed by ShivamKD. If you like GeeksforGeeks and would like to contribute, you can also write an article using write.geeksforgeeks.org or mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
 

Last Updated :
09 Jun, 2022

Like Article

Save Article

Решение уже написали, тем не менее считаю что в данном случае имеет смысл подробно разобрать ошибку автора.

Итак, исходное условие предложенное автором выглядит так

if (i % i == 0 & i / 1 == 0)

Разберем его на части:

  1. i % i == 0
    Это условие всегда истинно, т.к. остаток от деления любого числа на это же число всегда равен 0.

  2. i / 1 == 0.
    Это условие никогда не выполняется, т.к. результат деления любого числа на 1 всегда равен этому числу, но ни как не 0. Можно было бы его исправить на i % 1 == 0, но в этом случае условие будет всегда истинно, т.к. Остаток от деления на 1 всегда 0.

В итоге мы получаем либо всегда ложь, если не исправляем второе условие; либо всегда истину, если меняем деление на взятие остатка во втором условии.

Простое число — натуральное (целое положительное) число, имеющее ровно два различных натуральных делителя — единицу и самого себя.

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

PS: Жаль что приходится объяснять этот довольно простой по сути факт вместо вашего преподавателя математики, и далеко не факт что в этом виноват преподаватель. И учтите. что без хорошего знания математики в программировании делать то в общем нечего, так что восстанавливайте пробелы в математике пока не поздно.

Об античном ученом Эратосфене Киренском, которому приписывают наш метод, википедия говорит следующее [1]:

«Эратосфен Киренский (др.-греч. ἘρατοσθένηςὁΚυρηναῖος; 276 годдон. э.—194 годдон. э.) —греческий математик, астроном, географ, филолог и поэт. Ученик Каллимаха, с 235 г. дон. э. —глава Александрийской библиотеки. Первый известный учёный, вычисливший размеры Земли.»

Обычно метод Эратосфена описывают при помощи картинки, где изображено некоторое количество первых чисел натурального ряда (единицу, как и договорились, мы сразу выбросим из рассмотрения):

Первые 50 натуральных чисел

Первые 50 натуральных чисел

Идея в том, что мы берем подряд числа из натурального ряда. Для каждого взятого числа k добавляем это же число k и полученный результат вычеркиваем. Потом снова добавляем и снова вычеркиваем. Делаем так, пока не дойдем до конца интервала. Почему мы вычеркиваем эти числа? Потому что это каждое такое число кратно k, оно заведомо составное, а не простое.

Возьмем для начала двойку. Кстати, а саму двойку надо вычеркивать или нет? Ответ, с одной стороны, очевидный, с другой стороны – какой-то неожиданный. Этот ответ звучит так: если очередное  число  при проходе по натуральному ряду  еще не вычеркнуто, то и не надо его вычеркивать, оно «хорошее». Это как в сказке «Волшебная лампа Алладина» султан в сердцах постановил для своей непослушной дочери: «Отдам тебя замуж за первого встречного!». Вот мы начали перебирать наш ряд, взяли двойку. Мы ее встретили в первый раз, она еще не вычеркнута – значит, подходит! Это простое число – и мы его не вычеркиваем.

Но мы идем дальше. К двойке прибавляем само это число 2+2. Получилось 4. Надо ли здесь задавать себе вопрос: в первый раз или не в первый мы встретили это число? Нет, не надо. Потому что мы работаем с двойкой! Получившееся число 4 вычеркиваем.

Кстати, как долго нам надо идти вперед? Это зависит от точной постановки задачи, а мы пока этого не сделали, и подходили к задаче по-простецки – находили простые числа. Но мы не уточняли, сколько именно простых чисел мы хотим найти. В самом деле – сколько?

Поиск простых чисел на заданном интервале

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

Пусть это M равно 50. Значит,  надо добавлять нашу двойку, пока не дойдем до 50.

Начав с 2, и добавляя по 2, мы получим следующую картину (красным выделены вычеркнутые числа):

Вычеркнули от 2 по +2

Вычеркнули от 2 по +2

Теперь давайте разбираться с числом 3. Саму тройку мы оставляем по принципу «первого встречного», а дальше зачеркиваем 6, 9, 12, и так далее:

Вычеркнули от 3 по +3

Вычеркнули от 3 по +3

Нетрудно видеть, что какие-то числа нам пришлось вычеркивать по второму разу, начиная с той же шестерки – ведь мы уже вычеркнули 6, когда работали с числом 2. Есть ли в этом что-то нехорошее? Если рассматривать эту работу с вычислительной точки зрения, то как будто бы это нехорошо. Вообще  любая повторная работа, которую можно избежать – это нехорошо. Потому что мы тратим вычислительное время. Но в данном случае лучше все же закрыть глаза на эту избыточность. Потому что иначе нам пришлось бы как-то проверять, а не вычеркивали ли мы это число раньше?  А это, само по себе, тоже лишняя вычислительная работа.

С числом 3 разобрались. Надо ли нам разбираться с числом 4? Нет, принцип «первого встречного» говорит, что не надо – четверку мы уже встречали раньше, когда вычеркивали, начиная с  двойки.

Тогда идем дальше и смотрим на число 5. Пятерка – как раз «первый встречный», мы еще ее «не видели». Значит, оставляем 5 в «хороших числах», а вот 10, 15, 20, 25 и так далее до 50 (включительно) – вычеркиваем:

Вычеркнули от 5 по +5

Вычеркнули от 5 по +5

Число 6 снова пропускаем, зато семерка – «первый встречный», и мы вычеркиваем 21 (14 уже вычеркнули), 28, 35, 42, 49:

Вычеркнули от 7 по +7

Вычеркнули от 7 по +7

Как долго следует продолжать этот процесс? В самом начале мы сказали, что надо перебирать все числа в заданном интервале [2; M]. Но нетрудно догадаться, что на самом деле достаточно остановиться на числе M/2 (или его целой части для нечетных M). Ведь когда мы перейдем к следующему за ним числу K = [M/2] + 1, то число K + K заведомо окажется вне диапазона  [2; M].

В любом случае, когда мы закончим перебирать все числа в интервале [2; M]  (или только первую половину этого интервала), мы получим, что больше нам вычеркивать нечего. Это значит, что числа, оставшиеся незакчеркнуыми – и есть простые:

Вычеркнули от 7 по +7

Вычеркнули от 7 по +7

Всего мы нашли 16 простых чисел на нашем интервале, причем заранее мы не могли знать, сколько именно мы найдем. В этом и состоит главная прелесть и загадка простых чисел: не существует простого понимания того, насколько плотно простые числа расположены на числовой оси. Вспомним, какую постановку задачи мы сами для себя выбрали – найти все простые числа на заданном отрезке,  – то есть, не превосходящие некоторого числа M.

Это, конечно, здорово, но согласитесь – куда интереснее решить задачу о нахождении конкретного количества N простых чисел. На самом деле, с точки зрения некоторого вымышленного, но типового «заказчика», куда как важнее получить конкретное количество первых простых чисел, чем просто все, что можно найти на заданном интервале.

Давайте для начала запрограммируем на Python первую задачу – для заданного интервала, а потом станем думать, как это трансформировать в решение второй задачи.

# указываем верхнюю границу поиска

maxNum = 100

# создаем список вообще всех целых чисел от 0 до maxNum включительно

nums = list(range(maxNum + 1))

# мы будем рассматривать ненулевые числа; единицу тоже занулим сразу,

# потому что мы договорились, что это число не является простым

nums[1] = 0

# сюда будем складывать «женихов» – «первых встречных», т.е. найденные

# простые числа

primes = []

# внешний цикл – проход по заданному интервалу

for i in nums :

    # рассматриваем только ненулевые числа; 0 – значит число негодное,

    # мы его вычеркнули (не «первый встречный») 

    if i > 1:

        # Внутренний цикл: для каждого числа i проходим «прыжками» с шагом i       

        for j in range(i + i, len(nums), i):

            nums[j] = 0

        # не забываем сохранить само число

        primes.append(i)

print(len(primes))

В результате чего убедимся, что на отрезке [1; 100] всего 25 простых чисел. А теперь представим, что нашему заказчику совершенно все равно, какой интервал мы рассматриваем. Ему важно, чтобы мы нашли ему 100 первых простых чисел. А уж на каком интервале они лежат – как получится. Как нам нужно действовать?

Поиск заданного количества простых чисел

Наверно, самый простой ответ будет такой: давайте возьмем поисковый интервал «на глазок». Можно предположить, что это должен быть достаточно большой интервал. Например, выше мы убедились, безо всякого программирования, что на отрезке [1; 50] есть 16 простых чисел. Значит, если наш заказчик  хочет найти  100 простых чисел, можно взять поисковый интервал примерно от 1 до 300.

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

Это очень неприятная для нас новость. Если бы плотность простых чисел на числовой оси была хотя бы примерно постоянной, и тем более, если бы она уменьшалась с ростом номера простого числа,  то мы могли бы поступить именно предложенным способом. Но чем дальше в лес, тем реже встречаются простые числа. А это значит, что чем больше простых чисел нам надо найти, тем сильнее нам надо «раздвигать» поисковый интервал.

Кстати, почему это так? Это очень интересный вопрос, но им надо подробно заниматься не здесь, а в теории чисел. Но на самом деле, интуитивно, это и так ясно. Ведь чем больше число, тем больше шансов, что у него окажутся делители, большие единицы, но меньшие его самого.

Можем ли мы хотя бы оценить, насколько реже – в каком-то количественном исчислении – будут попадаться простые числа по мере продвижения по числовой оси? Наверное, можно. Но, опять же, сейчас это не наша тема. Пусть этим занимаются числовые теоретики.

Как же нам поступить? Предыдущее решение было слишком ненадежным, мы это уже поняли. Следующее предложение: давайте постепенно наращивать поисковый интервал. Скажем, мы обработали интервал [1; 50], нашли 16 простых чисел, а заказчику надо 100 – значит, у нас явно «недолет». Давайте искать заново, скажем, на удвоенном интервале [1; 100] (двойка – это хороший коэффициент, поверьте). Если снова найдем меньше чисел, чем надо – еще раз удвоим: будем искать на отрезке [1:200], и так далее. Пока не найдем столько простых чисел, сколько у нас просят. А если найдем больше, чем нужно – это не беда: больше – не меньше, лишние всегда можно отбросить.

В каком-то смысле, на этом месте можно было бы остановиться и считать задачу решенной. Мы нашли  принципиальный способ находить требуемое количество первых простых чисел. Но, к сожалению, с практической точки зрения,  этот способ очень плохой. Все дело в том, что нам придется многократно повторять одну и ту же работу. Представьте, что мы уже проделали все нужные вычисления на отрезке [1; M] и не нашли требуемое количество N простых чисел. Мы говорим: не беда! – и начинаем работу заново на отрезке [1; 2M]. И нам неизбежно придется пройти сначала по отрезку [1; M], но ведь мы это уже делали! Получается, мы сделаем эту работу повторно.

И это совсем не безобидная избыточность. Попробуем как-то оценить объем вычислительной работы. Пусть на начальном интервале [1: M] мы проделали условно m вычислительных операций. Сколько операций мы выполним при расширении поискового интервала? Давайте для оценки считать, что проработка любого интервала длины M требует одно и то же – m – количество вычислений. На самом деле это не так – выше мы уже поняли, что «чем дальше в лес», тем реже простые числа. Но у нас нет на руках никакого точного «закона», который говорил бы – насколько реже. Значит, нам ничего не остается, как довольствоваться той гипотезой, что у нас есть – пусть число вычислений на интервале длиной М всегда равняется m. На самом деле, мы таким образом завысим нашу оценку. Но с чего-то надо начать.

Вообще, такие оценки далеко не всегда легко выполнять, но программист постепенно должен этому учиться. Кроме этого, как и почти все в программировании, такие оценки можно делать по-разному. Конечно – при правильно проведенной оценке конечный результат не должен изменяться. Поэтому автор сделает так, как нравится ему. Изобразим наши поисковые интервалы геометрически:

Удвоили поисковый интервал

Удвоили поисковый интервал

Мы видим казалось бы – простую вещь: при увеличении поискового интервала в 2 раза объем вычислений увеличился в 2 раза. Однако – не совсем так. Ведь мы уже один раз проделали кусок работы на начальном интервале [1; M], а потом мы еще дважды ее выполнили. Всего получается уже
m + 2m = 3m.

Пусть нам снова не хватило простых чисел, найденных на интервале [1; 2M]. Тогда мы переходим к интервалу [1; 4M]. И мы видим, что полное количество проделанных нами вычислений от самого начала работы равно m + 2m +4m = 7m.

Еще раз удвоили поисковый интервал

Еще раз удвоили поисковый интервал

Теперь мы уже можем разглядеть, по какому закону нарастает объем вычислений – это закон геометрической прогрессии. Поскольку знаменатель этой прогрессии равен 2, сумма k членов прогрессии равна m * (2^k – 1) / (2 -1), то есть – m * (2^k – 1). Напомним: k слагаемых в нашей сумме означает, что мы k-1 раз удваивали размер поискового интервала.

Это не просто очень сильный рост, это самый плохой рост, какой только можно вообразить, и любой ИТ-специалист сразу схватится за голову: такой алгоритм просто никуда не годится. Надо искать какой-то другой метод.

Нам снова поможет довольно простая идея: давайте, вместо того, чтобы все время начинать работу заново, попробуем просто продолжать эту работу. Легко сказать, а как сделать?

Если бы мы проверяли только деление, скажем на 2 или 3, или любое другое, но одно конкретное число,  мы бы шли и шли вдоль первого интервала, потом увидели бы, что одного этого интервала нам не хватае, и пошли бы дальше, и нет проблем. Но проблема в том, что проработав число 2, мы его бросаем, и работаем с числом 3, и так далее. А когда приходит время расширить интервал – мы уже ничего не помним, и поэтому не знаем, за что хвататься.

Какое число не возьми – ту же двойку – мы не знаем, откуда надо начинать новый отсчет.

Хорошая формулировка проблемы часто содержит, частично или даже полностью,  путь к ее решению, и это именно наш случай. Если мы не знаем, откуда нам начинать прыгать через 2 (3, 5, 7 – и так далее) – давайте просто будем это записывать, чтобы не забыть.

Запоминать лучше в виде словаря. Словарь – это простейший объект в языках Python и JavaScript. Возможно, и в каких-то других языках тоже, но кругозор автора не бесконечен. А в этих двух языках словарь представляет собой перечень пар <ключ> : <значение>, заключенный в фигурные скобки {}:

{

            <ключ_1> : <значение_1>,

<ключ_2> : <значение_2>,

<ключ_3> : <значение_3>,

}

В качестве ключа могут применяться разные типы данных, но мы для простоты будем считать, что у нас ключами будут числа (чаще всего применяют строки). Значение может быть вообще любое: число, строка, список, или любой другой объект (не обязательно словарь – есть еще экземпляры классов, но мы в эту область не заглядываем).

В языках Си и С++ существует похожий тип данных, называемый структурой (struct), но это менее гибкий тип данных, чем словарь Python. Любопытно, что в JavaScript вообще все объекты, включая очень сложные, строятся на основе словарей, а классов, как в С++, Java или Python, нет вообще, но можно наследовать объекты. В этом отношении, JavaScript – самый необычный объектно-ориентированный язык.

Мы немного отклонились от нашей основной задачи, но только для того, чтобы подсветитить, насколько словарь – действительно важнейший тип данных, и далеко не в одном только Python, на котором мы решаем нашу задачу.

Что именно нам надо запоминать? Во-первых, необходимо запоминать сами простые числа – все, что мы нашли к текущему моменту. Во-вторых, для каждого такого числа надо запоминать – внимание! – последнее число, которое мы вычеркнули, удаляя числа «по поводу» этого простого числа.

Возможно, это не самая простая и понятная формулировка, поэтому давайте повторим ее другими словами. Допустим, мы нашли простое число 2. Мы последовательно вычеркивали, в нашем текущем интервале, числа: 4, 6, 8, … и остановились, например, на числе 240. А наш интервал поиска заканчивался, например, на числе 241. Дальше нам идти некуда – в рамках данного интервала.

Но ведь далее мы расширим наш интервал и пойдем дальше. Для этого мы должны знать, что в предудыщий раз мы остановились на числе 240. Остановились, потому что число 240+2=242 лежало уже вне прошлого интервала. Но оно уже лежит в новом интервале. Начиная с этого числа, можно продолжать работу. Поэтому число 240 надо запомнить, и его надо запомнить «по поводу» простого числа 2.

Запоминать эту пару чисел мы будем в виде одной записи в словаре:

{

2 : 240,

}

            Назовем наш словарь pev_primes от слов “previous primes” – «предыдущие простые». Это название отражает тот факт, что, приступая к работе с новым интервалом, у нас уже есть запас информации, наработанный на предыдущих интервалах. На самом деле, здесь будут не только «предыдущие» числа,  ведь мы будем оперативно добавлять сюда только что найденные простые числа. Но, все-таки, это название напомнит нам, что мы работаем с накопленными данными.

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

            Дополним наш предыдущий код заполнением словаря pev_primes по мере того, как мы проходим по начальному поисковому диапазону [1; maxNum]:

maxNum = 100

nums = list(range(maxNum + 1))

nums[1] = 0

prev_primes = {}

# внешний цикл – проход по заданному интервалу

for i in nums :

    if i > 1:

        # Внутренний цикл: для каждого числа i проходим «прыжками» с шагом i

        for j in range(i + i, len(nums), i):

            nums[j] = 0

Последнее значение j и есть то число, последнее для i, которое мы вычеркнули в текущем интервале, поэтому его надо запомнить.

        prev_primes[i] = j

          Давайте посмотрим, что мы сохранили:

print(prev_primes)

Что-то пошло не так

Что-то пошло не так

Это только первый вопрос, но есть еще и другой: почему значение 94 у ключа 53 такое же, как у ключа 47. Есть и третий вопрос: почему значение 94 так и повторяется до самого конца словаря pev_primes.

Обнаружить причину произошедшего на самом деле несложно. Число 53 – первое, большее половины нашего диапазона. Если мы добавим 53 к 53, мы сразу выходим за пределы поискового диапазона. Это значит, что цикл

 for j in range(i + i, len(nums), i):

– даже не начнет выполняться. Тогда, что же мы будем записывать в наш словарь в инструкции prev_primes[i] = j ? Это будет то значение j, которое осталось от предыдущего i, при котором цикл for еще выполнялся – как раз 94.

Значит, нужно ввести какую-то проверку, что цикл for вообще выполняется, что поток исполнения заходит «внутрь» тела цикла. Давайте сделаем это «по-питонски»: создадим сразу список на удаление, и тогда увидим – пустой он или нет.

for i in nums :

    if i > 1:  

        to_remove = range(i + i, len(nums)+1,

        for j in to_remove :

            nums[j] = 0

        if len(to_remove) > 0:

            prev_primes[i] = j

print(prev_primes)

Теперь стало лучше

Теперь стало лучше

            Теперь все хорошо, нет «паразитных» записей в нашем словаре. Настал момент научиться использовать накопленные данные при расширении поискового диапазона. Давайте оформим эту работу в виде функции, которой мы будем передавать в качестве аргументов границы очередного поискового диапазона (минимальное minNum и максимальное число maxNum в этом диапазоне), а также имеющийся на данный момент словарь найденных простых чисел prev_primes :

def add_primes(minNum, maxNum, prev_primes) :

# создаем список, отображающий новый поисковый интервал

nums = list(range(minNum, maxNum+1))

          # получаем сразу - в виде кортежа (prime, last_removed)

          # - простое число и его «последнее удаленное»

          for prime, last_removed in prev_primes.items():

          Cписок на удаление создается чуть более хитро, чем ранее: мы добавляем к «последнему удаленному» его ключ – простое число. Ведь последнее удаленное было в предыдущем интервале, а следующее за ним уже не помещалось в тот интервал, но зато оно как раз попадает в новый (текущий) поисковый интервал. С него и начнем удалять:

          to_remove = range(last_removed  + prime, maxNum+1, prime)

            И удалять числа придется тоже чуть похитрее, чем раньше: чтобы получить индекс нужного числа в списке nums, нужно из этого числа вычесть начало диапазона:

#def add_primes(minNum, maxNum, prev_primes) :

#        …

          # for prime, last_removed in prev_primes.items():

# …

          for num in to_remove :

nums[num - minNum] = 0

Снова запоминаем «последнее удаленное» число, а это ни что иное, как последний элемент в списке на удаление:

#def add_primes(minNum, maxNum, prev_primes) :

#        …

          # for prime, last_removed in prev_primes.items():

# …

prev_primes[prime] = to_remove[-1]

Итак, что мы сделали. Мы продолжили вычеркивание чисел, кратным тем простым, которые мы нашли на предыдущем интервале, точнее – на всех предыдущих интервалах. Мы же будем продолжать расширять поисковый интервал неопределенно долго. Но на текущем интервале должны найтись какие-то новые простые числа в списке nums!

def add_primes(minNum, maxNum, prev_primes) :

nums = list(range(minNum, maxNum+1))

# ищем новые простые

for num in nums :

            if num != 0:    

Первое же встречное, но обязательно ненулевое – т.е. не вычеркнутое число, и есть простое, как и раньше.

                        next_prime = num

Как и раньше, удалять надо с числа next_prime + next_prime, с шагом next_prime.

                         to_remove = range(next_prime * 2, maxNum+1, next_prime)

            for num1 in to_remove:

                  nums[num1 - minNum] = 0

Как и раньше, к данному простому числу next_prime привязываем то число, которое мы вычеркнули последним:

                            if len(to_remove) > 0:

                    prev_primes[next_prime] = to_remove[-1]

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

#Находит не менее первых n простых чисел

def get_n_primes(n):

#начальный поисковый диапазон – от 2 до 2*n

          minNum = 2

     maxNum = 2*n

     prev_primes = {}

# будем считать, сколько раз мы раздвигаем начальный интервал

cnt = 0

          while len(prev_primes) < n:

            cnt += 1

            print(cnt)               

# ключи словаря и есть те простые числа, что мы нашли

# на данный момент

                    print(list(prev_primes.keys()))

            add_primes(minNum, maxNum, prev_primes)

                    # расширяем интервал: добавляем справа такой же по длине

                    minNum = maxNum + 1

            maxNum *= 2

          return list(prev_primes.keys())

Наконец, выполняем все вместе:

a = get_n_prime(100)

Выше уже говорилось, что  мы не можем так подгадать, чтобы найти ровно требуемое (например, 100) количество простых чисел. Но мы можем проверить, что нашли не меньше, и в этом месте прекращаем развдигать наше раздвижное сито. И, чтобы вывести ровно 100 чисел, просто возьмем срез от найденного списка:

a = a[:100]

print(a)

Однако, эта программа почему-то «зависает»: мы увидим, что счетчик итераций cnt безостановочно увеличивается, а список найденных простых чисел не растет:

14

[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]

15

[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]

16

[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]

Мы видим нечто такое, на что вовсе не расчитывали: набор простых чисел перестал расти. Как понять – что мы делаем не так? Добавим отладочную печать в том месте, где мы пополняем наше хранилище. Будем выводить найденное простое число и то «последнее вычеркнутое», которое с ним связано.

    for num in nums :

        if num != 0:

            …

            next_prime = num

            print(next_prime, end=' : ')  

            if len(to_remove) > 0:

                print(to_remove[-1], end='n')

                prev_primes[next_prime] = to_remove[-1]

Получаем такую трассировку нашей программы:

2 : 200

3 : 198

89 : 178

97 : 194

101 : 103 : 107 : 109 : 113 : …

Итак, что происходит? Новые простые числа – появляются, но мы ничего не записываем «по этому поводу». Почему? Есть только одна возможность: не выполняется условие if len(to_remove) > 0, то есть – список to_remove попросту пустой, но почему? Сразу сообщим правильную догадку (к ней, в самом деле, несложно прийти): next_prime * 2 (он же next_prime + next_prime) в какой-то момент оказывается больше maxNum, и список (точнее, итератор) to_remove оказывается пустым. Список пустой – значит, ничего и не вычеркиваем, работа дальше не идет. 

Точнее говоря – работа идет, но недостаточная: мы вычеркиваем числа, кратные только ранее найденным простым, а новые простые мы не находим и не и не используем.

То есть, мы с самого начала – по умолчанию – решили, что «последнее вычеркнутое» число мы будем запоминать, только если оно находится в пределах текущего поискового интервала (меньше maxNum), но так может вовсе и не оказаться. Идея была разумная: как мы можем запомнить «последнее вычеркнутое», если мы его вовсе не вычеркивали! Мы же можем вычеркнуть только в пределах текущего поискового интервала.

Значит, нам необходимо запоминать «последнее вычеркнутое» даже в том случае, если мы не вычеркивали. Ого! Вот это авантюра, вот это приписки! На самом деле, ничего страшного – просто, мы его вычеркнем потом – когда перейдем от текущего поискового интервала к следующему. Для этого, создавать список на удаление to_remove каждый раз придется чуть более аккуратно, чем ранее.

Это окажется последней настройкой нашей задумки, программа заработает уже полностью правильно, в чем легко может убедиться любой начинающий кибернетик – программист галактических искусственных разумов. Последние добавления в программу выделены отдельно.

def add_primes(minNum, maxNum, prev_primes):

    '''добавляем простые числа и последние вычеркнутые числа'''

    nums = list(range(minNum, maxNum+1)

    for prime, last_removed in prev_primes.items():

        if last_removed < minNum :

            to_remove = range(last_removed + prime, maxNum+1, prime)

        else :

            to_remove = range(last_removed, maxNum+1, prime)

        for num in to_remove :

            nums[num - minNum] = 0

        prev_primes[prime] = to_remove[-1]

    # ищем новые простые

    for num in nums :

        # если равен нулю, то число вычеркнуто

        if num != 0:

            next_prime = num

            to_remove = range(next_prime * 2, maxNum+1, next_prime)

            for num1 in to_remove:

                nums[num1 - minNum] = 0

            if len(to_remove) > 0:

                prev_primes[next_prime] = to_remove[-1]

            else :

                prev_primes[next_prime] = 2 * next_prime

def get_n_primes(n):

    #Находит первые n или больше первых простых чисел

    minNum = 2

    maxNum = 2*n

    prev_primes = {}

    while len(prev_primes) < n:

        add_primes(minNum, maxNum, prev_primes)

        minNum = maxNum + 1

        maxNum *= 2

    return list(prev_primes.keys())

a = get_n_prime(100)

a = a[:100]

print(a)

Проверяем результат: (можете сравнить с готовыми таблицами простых чисел):.

[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, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]

Желающие могут получить готовый код на onlinegdb.com: https://onlinegdb.com/sQhflSeJ3

Напоследок – вопрос для размышлений, на будущее. Хорошо, мы справились с задачей. Но мы справились типично «по-питонски»: с использованием словарей. Не все языки поддерживают такой тип данных. Как упоминалось выше, в языке Си есть аналог в виде struct, но это совсем не то – это можно считать словарем, но фиксированной длины, а в Python словарь «резиновый». Как построить алгоритм «раздвижного решета» для языка Си или С++?

Литература:

1.     Эратосфен. Статья в электронной энциклопедии «Википедия» https://ru.wikipedia.org/wiki/Эратосфен

2.     Гончаренко В.Е. «Определение простых чисел от века папируса до века ПК», журнал «Потенциал» № 1,  2010 г.

3.     Гончаренко В.Е. «Алгоритм создания коллекции простых чисел», журнал «Потенциал» № 3,  2010 г.

Простое число — это натуральное число, которое больше 1 и не имеет положительного делителя, кроме 1 и самого себя, например 2, 3, 5, 7, 11, 13 и так далее.

Пользователю даются два целых числа, нижнее значение и верхнее значение. Задача состоит в том, чтобы написать программу Python для вывода всех простых чисел в заданном интервале (или диапазоне).

Чтобы напечатать все простые числа в заданном интервале, пользователь должен выполнить следующие шаги:

  • Шаг 1: Переберите все элементы в заданном диапазоне.
  • Шаг 2: Проверьте для каждого числа, есть ли у него какой-либо множитель между 1 и самим собой.
  • Шаг 3: Если да, то число не простое, и оно перейдет к следующему числу.
  • Шаг 4: Если нет, то это простое число, и программа распечатает его и проверит следующее число.
  • Шаг 5: Цикл прервется, когда будет достигнуто верхнее значение.

Пример: код Python для печати простого числа в заданном интервале.

 
# First, we will take the input: 
lower_value = int(input("Please, Enter the Lowest Range Value: ")) 
upper_value = int(input("Please, Enter the Upper Range Value: ")) 
 
print("The Prime Numbers in the range are: ") 
for number in range(lower_value, upper_value + 1): 
    if number > 1: 
        for i in range(2, number): 
            if(number % i) == 0: 
                break 
        else: 
            print(number) 

Выход:

Please, Enter the Lowest Range Value:  14 
Please, Enter the Upper Range Value:  97 
The Prime Numbers in the range are:  
17 
19 
23 
29 
31 
37 
41 
43 
47 
53 
59 
61 
67 
71 
73 
79 
83 
89 
97 

Заключение

В этом уроке мы показали, как написать код для печати простых чисел в заданном диапазоне чисел.

Изучаю Python вместе с вами, читаю, собираю и записываю информацию опытных программистов.

Как найти простые числа после 100 “Идея алгоритма Найдите все простые числа от 1 до sqrt(n) с помощью простого сита. Разделим весь диапазон(1 n) на ceil(n/sqrt(n)) количество сегментов размером sqrt(n) каждый. Для каждого сегмента мы выполняем просеивание. Начало и конец – это первый и последний элементы текущего сегмента. . “.

Как найти количество простых чисел в диапазоне

Содержание

  1. Как найти простые числа от 100 до 150
  2. Как найти простые числа-близнецы между 100 и 200
  3. Каковы правила простых чисел
  4. Как найти простые числа между 120 и 140
  5. Как найти простые числа от 100 до 120
  6. Почему не существует формулы для простых чисел
  7. Почему 27 не является простым числом
  8. Как найти количество простых чисел между двумя числами
  9. Как вычислить простые числа-близнецы
  10. Как найти простые числа между 150 и 200

Рекомендую! Смотрите в обзоре: Какой самый простой способ вдеть нитку в маленькую иголку в 2023.

Как найти простые числа от 100 до 150

Как найти простые числа от 100 до 200 “Hecne 101 103 107 109 113 127 131 137 139 и являются простыми числами между и. “.

Рекомендую! в статье: Какое лучшее оружие ближнего боя в Warframe 2023 в 2023.

Как найти простые числа-близнецы между 100 и 200

Как найти простые числа между 100 и 200 “Разность между 197 и 199 равна 2. Значит, (197 199) – пара простых чисел-близнецов. Поэтому парами простых чисел-близнецов являются (101 103) (107 109) (137 139) (149 151) (179 181) (191 193) (197 199). “.

Рекомендую! Узнайте в обзоре: Где должны быть установлены датчики движения в 2023.

Каковы правила простых чисел

Какова формула для нахождения простых чисел “Простое число – это число, которое имеет только два фактора: себя и 1. Или, другими словами, оно может быть поделено поровну только на себя и 1. Например, 3 – простое число, потому что оно может быть поровну поделено только на себя и 1. С другой стороны, 6 можно разделить поровну на 1 2 3 и 6.

Рекомендую! Узнайте здесь: Какой самый трудный возраст для котенка в 2023.

Как найти простые числа между 120 и 140

Как найти простые числа от 100 до 150 “131 133 137 139 – четыре простых числа между 120 и 140. “.

Рекомендую! здесь: Как получить 6 полностью заряженных маяков в 2023.

Как найти простые числа от 100 до 120

Как найти простые числа после 100 ” Простыми числами между 100 и 120 являются 101 103 107 109 113, поэтому их сумма равна 533. “.

Почему не существует формулы для простых чисел

Какова формула для нахождения простых чисел “В теории чисел формула для простых чисел – это формула, порождающая простые числа точно и без исключений. Такой формулы, которая бы эффективно вычислялась, не существует. Известен ряд ограничений, показывающих, чем может и чем не может быть такая “”формула””. “.

Почему 27 не является простым числом

Как найти простые числа после 100 “Является ли 27 простым числом Нет. Факторы 27 – 1 3 9 и 27, поэтому оно не является простым. “.

Как найти количество простых чисел между двумя числами

Как найти двойное простое число между 100 и 200.

Как вычислить простые числа-близнецы

Как найти двойные простые числа между 100 и 200 “Чтобы найти пару двойных простых чисел, нужно проверить, что числа являются простыми и разница между двумя простыми числами равна 2. Давайте сначала возьмем 3 и 5. Таким образом, 4 пары простых чисел-близнецов – это (3 5) (5 7) (11 13) и (17 19). “.

Как найти простые числа между 150 и 200

Как найти простые числа от 100 до 150 “Следовательно, 151 157 163 167 173 179 181 191 193 197 и являются простыми числами между и. “.

Все права защищены. Несанкционированное копирование, полностью или частично, строго запрещено.

Добавить комментарий